From fab74d8092f5b5f83beb505a4c75a25f62ee050f Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Thu, 19 Jun 2014 19:15:42 +0400 Subject: [PATCH] Smart enter for while statement #KT-3600 In Progress --- .../jet/lang/psi/JetWhileExpression.java | 16 +++- .../plugin/editor/KotlinSmartEnterHandler.kt | 35 ++++--- .../editor/fixers/KotlinIfConditionFixer.kt | 40 ++------ .../fixers/KotlinMissingIfBranchFixer.kt | 2 +- .../fixers/KotlinMissingWhileBodyFixer.kt | 43 +++++++++ .../fixers/KotlinWhileConditionFixer.kt | 30 ++++++ .../editor/fixers/MissingConditionFixer.kt | 64 +++++++++++++ .../jet/plugin/editor/fixers/fixersUtil.kt | 1 + .../codeInsight/smartEnter/SmartEnterTest.kt | 94 +++++++++++++++++++ 9 files changed, 275 insertions(+), 50 deletions(-) create mode 100644 idea/src/org/jetbrains/jet/plugin/editor/fixers/KotlinMissingWhileBodyFixer.kt create mode 100644 idea/src/org/jetbrains/jet/plugin/editor/fixers/KotlinWhileConditionFixer.kt create mode 100644 idea/src/org/jetbrains/jet/plugin/editor/fixers/MissingConditionFixer.kt diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetWhileExpression.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetWhileExpression.java index 8f9218064a6..3b43b5d64bb 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetWhileExpression.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetWhileExpression.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 JetBrains s.r.o. + * Copyright 2010-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. @@ -17,7 +17,10 @@ package org.jetbrains.jet.lang.psi; import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lexer.JetTokens; public class JetWhileExpression extends JetWhileExpressionBase { public JetWhileExpression(@NotNull ASTNode node) { @@ -28,4 +31,15 @@ public class JetWhileExpression extends JetWhileExpressionBase { public R accept(@NotNull JetVisitor visitor, D data) { return visitor.visitWhileExpression(this, data); } + + @Nullable + @IfNotParsed + public PsiElement getLeftParenthesis() { + return findChildByType(JetTokens.LPAR); + } + + @Nullable @IfNotParsed + public PsiElement getRightParenthesis() { + return findChildByType(JetTokens.RPAR); + } } diff --git a/idea/src/org/jetbrains/jet/plugin/editor/KotlinSmartEnterHandler.kt b/idea/src/org/jetbrains/jet/plugin/editor/KotlinSmartEnterHandler.kt index 9c5129dcb8d..1a9029cc7f7 100644 --- a/idea/src/org/jetbrains/jet/plugin/editor/KotlinSmartEnterHandler.kt +++ b/idea/src/org/jetbrains/jet/plugin/editor/KotlinSmartEnterHandler.kt @@ -29,21 +29,26 @@ import com.intellij.psi.PsiWhiteSpace import org.jetbrains.jet.lang.psi.JetDeclaration import org.jetbrains.jet.lang.psi.JetBlockExpression import org.jetbrains.jet.lang.psi.JetExpression -import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrIfStatement import org.jetbrains.jet.lang.psi.JetDeclarationWithBody import org.jetbrains.jet.lang.psi.JetIfExpression +import org.jetbrains.jet.plugin.editor.fixers.KotlinWhileConditionFixer +import org.jetbrains.jet.plugin.editor.fixers.KotlinMissingWhileBodyFixer +import org.jetbrains.jet.lang.psi.JetWhileExpression +import org.jetbrains.jet.plugin.editor.fixers.isWithCaret import com.intellij.psi.tree.TokenSet -import org.jetbrains.jet.lexer.JetTokens import org.jetbrains.jet.JetNodeTypes public class KotlinSmartEnterHandler: SmartEnterProcessorWithFixers() { { addFixers( - KotlinMissingIfBranchFixer, - KotlinIfConditionFixer + KotlinIfConditionFixer(), + KotlinMissingIfBranchFixer(), + + KotlinWhileConditionFixer(), + KotlinMissingWhileBodyFixer() ) - addEnterProcessors(KotlinPlainEnterProcessor) + addEnterProcessors(KotlinPlainEnterProcessor()) } override fun getStatementAtCaret(editor: Editor?, psiFile: PsiFile?): PsiElement? { @@ -99,16 +104,16 @@ public class KotlinSmartEnterHandler: SmartEnterProcessorWithFixers() { private fun PsiElement.isJetStatement() = getParent() is JetBlockExpression || (getParent()?.getNode()?.getElementType() in IF_BRANCHES_CONTAINERS) - object KotlinPlainEnterProcessor : SmartEnterProcessorWithFixers.FixEnterProcessor() { - private fun getControlStatementBlock(caret: Int, element: PsiElement): JetBlockExpression? { - if (element is JetDeclarationWithBody) return element.getBodyExpression() as? JetBlockExpression - if (element is JetIfExpression) { - if (element.getThen()?.getTextRange()?.contains(caret) == true) { - return element.getThen() as? JetBlockExpression + class KotlinPlainEnterProcessor : SmartEnterProcessorWithFixers.FixEnterProcessor() { + private fun getControlStatementBlock(caret: Int, element: PsiElement): JetExpression? { + when (element) { + is JetDeclarationWithBody -> return element.getBodyExpression() + is JetIfExpression -> { + if (element.getThen().isWithCaret(caret)) return element.getThen() + if (element.getElse().isWithCaret(caret)) return element.getElse() } - - if (element.getElse()?.getTextRange()?.contains(caret) == true) { - return element.getElse() as? JetBlockExpression + is JetWhileExpression -> { + if (element.getBody()?.getTextRange()?.contains(caret) == true) return element.getBody() } } @@ -116,7 +121,7 @@ public class KotlinSmartEnterHandler: SmartEnterProcessorWithFixers() { } override fun doEnter(atCaret: PsiElement, file: PsiFile?, editor: Editor, modified: Boolean): Boolean { - val block = getControlStatementBlock(editor.getCaretModel().getOffset(), atCaret) + val block = getControlStatementBlock(editor.getCaretModel().getOffset(), atCaret) as? JetBlockExpression if (block != null) { val firstElement = block.getFirstChild()?.getNextSibling() diff --git a/idea/src/org/jetbrains/jet/plugin/editor/fixers/KotlinIfConditionFixer.kt b/idea/src/org/jetbrains/jet/plugin/editor/fixers/KotlinIfConditionFixer.kt index 4dce64aa8d2..621476c3ce1 100644 --- a/idea/src/org/jetbrains/jet/plugin/editor/fixers/KotlinIfConditionFixer.kt +++ b/idea/src/org/jetbrains/jet/plugin/editor/fixers/KotlinIfConditionFixer.kt @@ -24,37 +24,11 @@ import org.jetbrains.jet.lang.psi.JetIfExpression import com.intellij.psi.util.PsiTreeUtil import com.intellij.openapi.util.TextRange -object KotlinIfConditionFixer : SmartEnterProcessorWithFixers.Fixer() { - override fun apply(editor: Editor, processor: KotlinSmartEnterHandler, element: PsiElement) { - if (element !is JetIfExpression) return - val ifExpression = element as JetIfExpression - - val doc = editor.getDocument() - val lParen = ifExpression.getLeftParenthesis() - val rParen = ifExpression.getRightParenthesis() - val condition = ifExpression.getCondition() - - if (condition == null) { - if (lParen == null || rParen == null) { - var stopOffset = doc.getLineEndOffset(doc.getLineNumber(ifExpression.range.start)) - val then = ifExpression.getThen() - if (then != null) { - stopOffset = Math.min(stopOffset, then.range.start) - } - - stopOffset = Math.min(stopOffset, ifExpression.range.end) - - doc.replaceString(ifExpression.range.start, stopOffset, "if ()") - processor.registerUnresolvedError(ifExpression.range.start + "if (".length()) - } - else { - processor.registerUnresolvedError(lParen.range.end) - } - } - else { - if (rParen == null) { - doc.insertString(condition.range.end, ")") - } - } - } +public class KotlinIfConditionFixer : MissingConditionFixer() { + override val keyword = "if" + override fun getElement(element: PsiElement?) = element as? JetIfExpression + override fun getCondition(element: JetIfExpression) = element.getCondition() + override fun getLeftParenthesis(element: JetIfExpression) = element.getLeftParenthesis() + override fun getRightParenthesis(element: JetIfExpression) = element.getRightParenthesis() + override fun getBody(element: JetIfExpression) = element.getThen() } \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/editor/fixers/KotlinMissingIfBranchFixer.kt b/idea/src/org/jetbrains/jet/plugin/editor/fixers/KotlinMissingIfBranchFixer.kt index f8a3c1f3b7d..f346a69dec3 100644 --- a/idea/src/org/jetbrains/jet/plugin/editor/fixers/KotlinMissingIfBranchFixer.kt +++ b/idea/src/org/jetbrains/jet/plugin/editor/fixers/KotlinMissingIfBranchFixer.kt @@ -25,7 +25,7 @@ import org.jetbrains.jet.lang.psi.JetIfExpression import org.jetbrains.jet.plugin.formatter.JetBlock import org.jetbrains.jet.lang.psi.JetBlockExpression -object KotlinMissingIfBranchFixer : SmartEnterProcessorWithFixers.Fixer() { +public class KotlinMissingIfBranchFixer : SmartEnterProcessorWithFixers.Fixer() { override fun apply(editor: Editor, processor: KotlinSmartEnterHandler, element: PsiElement) { if (element !is JetIfExpression) return val ifExpression = element as JetIfExpression diff --git a/idea/src/org/jetbrains/jet/plugin/editor/fixers/KotlinMissingWhileBodyFixer.kt b/idea/src/org/jetbrains/jet/plugin/editor/fixers/KotlinMissingWhileBodyFixer.kt new file mode 100644 index 00000000000..ea8758b5530 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/editor/fixers/KotlinMissingWhileBodyFixer.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2010-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 org.jetbrains.jet.plugin.editor.fixers + +import com.intellij.lang.SmartEnterProcessorWithFixers +import org.jetbrains.jet.plugin.editor.KotlinSmartEnterHandler +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiElement +import org.jetbrains.jet.lang.psi.JetWhileExpression +import org.jetbrains.jet.lang.psi.JetBlockExpression + +public class KotlinMissingWhileBodyFixer: SmartEnterProcessorWithFixers.Fixer() { + override fun apply(editor: Editor, processor: KotlinSmartEnterHandler, element: PsiElement) { + if (element !is JetWhileExpression) return + val whileStatement = element as JetWhileExpression + + val doc = editor.getDocument() + + val body = whileStatement.getBody() + if (body is JetBlockExpression) return + if (body != null && body.startLine(doc) == whileStatement.startLine(doc) && whileStatement.getCondition() != null) return + + val rParenth = whileStatement.getRightParenthesis() + if (rParenth == null) return + + doc.insertString(rParenth.range.end, "{}") + } +} + diff --git a/idea/src/org/jetbrains/jet/plugin/editor/fixers/KotlinWhileConditionFixer.kt b/idea/src/org/jetbrains/jet/plugin/editor/fixers/KotlinWhileConditionFixer.kt new file mode 100644 index 00000000000..7cc68128117 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/editor/fixers/KotlinWhileConditionFixer.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2010-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 org.jetbrains.jet.plugin.editor.fixers + +import org.jetbrains.jet.lang.psi.JetIfExpression +import com.intellij.psi.PsiElement +import org.jetbrains.jet.lang.psi.JetWhileExpression + +public class KotlinWhileConditionFixer: MissingConditionFixer() { + override val keyword = "while" + override fun getElement(element: PsiElement?) = element as? JetWhileExpression + override fun getCondition(element: JetWhileExpression) = element.getCondition() + override fun getLeftParenthesis(element: JetWhileExpression) = element.getLeftParenthesis() + override fun getRightParenthesis(element: JetWhileExpression) = element.getRightParenthesis() + override fun getBody(element: JetWhileExpression) = element.getBody() +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/editor/fixers/MissingConditionFixer.kt b/idea/src/org/jetbrains/jet/plugin/editor/fixers/MissingConditionFixer.kt new file mode 100644 index 00000000000..5428f9bb235 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/editor/fixers/MissingConditionFixer.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2010-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 org.jetbrains.jet.plugin.editor.fixers + +import com.intellij.lang.SmartEnterProcessorWithFixers +import org.jetbrains.jet.plugin.editor.KotlinSmartEnterHandler +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiElement + +abstract class MissingConditionFixer() : SmartEnterProcessorWithFixers.Fixer() { + override fun apply(editor: Editor, processor: KotlinSmartEnterHandler, element: PsiElement) { + val workElement = getElement(element) + if (workElement == null) return + + val doc = editor.getDocument() + val lParen = getLeftParenthesis(workElement) + val rParen = getRightParenthesis(workElement) + val condition = getCondition(workElement) + + if (condition == null) { + if (lParen == null || rParen == null) { + var stopOffset = doc.getLineEndOffset(doc.getLineNumber(workElement.range.start)) + val then = getBody(workElement) + if (then != null) { + stopOffset = Math.min(stopOffset, then.range.start) + } + + stopOffset = Math.min(stopOffset, workElement.range.end) + + doc.replaceString(workElement.range.start, stopOffset, "$keyword ()") + processor.registerUnresolvedError(workElement.range.start + "$keyword (".length()) + } + else { + processor.registerUnresolvedError(lParen.range.end) + } + } + else { + if (rParen == null) { + doc.insertString(condition.range.end, ")") + } + } + } + + abstract val keyword: String + abstract fun getElement(element: PsiElement?): T? + abstract fun getCondition(element: T): PsiElement? + abstract fun getLeftParenthesis(element: T): PsiElement? + abstract fun getRightParenthesis(element: T): PsiElement? + abstract fun getBody(element: T): PsiElement? +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/editor/fixers/fixersUtil.kt b/idea/src/org/jetbrains/jet/plugin/editor/fixers/fixersUtil.kt index 2f2e7b11511..7b95cb43fba 100644 --- a/idea/src/org/jetbrains/jet/plugin/editor/fixers/fixersUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/editor/fixers/fixersUtil.kt @@ -25,3 +25,4 @@ val TextRange.start: Int get() = getStartOffset() val TextRange.end: Int get() = getEndOffset() fun PsiElement.startLine(doc: Document): Int = doc.getLineNumber(range.start) +fun PsiElement?.isWithCaret(caret: Int) = this?.getTextRange()?.contains(caret) == true \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/codeInsight/smartEnter/SmartEnterTest.kt b/idea/tests/org/jetbrains/jet/plugin/codeInsight/smartEnter/SmartEnterTest.kt index 846df459348..25945311d2f 100644 --- a/idea/tests/org/jetbrains/jet/plugin/codeInsight/smartEnter/SmartEnterTest.kt +++ b/idea/tests/org/jetbrains/jet/plugin/codeInsight/smartEnter/SmartEnterTest.kt @@ -266,6 +266,100 @@ class SmartEnterTest : JetLightCodeInsightFixtureTestCase() { """ ) + + fun testWhile() = doFunTest( + """ + while + """ + , + """ + while () { + } + """ + ) + + fun testWhile2() = doFunTest( + """ + while + """ + , + """ + while () { + } + """ + ) + + fun testWhile3() = doFunTest( + """ + while ( + """ + , + """ + while () { + } + """ + ) + + fun testWhile4() = doFunTest( + """ + while (true) { + } + """ + , + """ + while (true) { + + } + """ + ) + + fun testWhile5() = doFunTest( + """ + while (true) { + """ + , + """ + while (true) { + + } + """ + ) + + fun testWhile6() = doFunTest( + """ + while (true) { + println() + } + """ + , + """ + while (true) { + + println() + } + """ + ) + + fun testWhile7() = doFunTest( + """ + while () + """, + """ + while () { + } + """ + ) + + fun testWhileSingle() = doFunTest( + """ + while (true) println() + """, + """ + while (true) println() + + """ + ) + fun doFunTest(before: String, after: String) { fun String.withFunContext(): String { val bodyText = "//----${this.trimIndent()}//----"