Smart enter for while statement

#KT-3600 In Progress
This commit is contained in:
Nikolay Krasko
2014-06-19 19:15:42 +04:00
parent 693080acfc
commit fab74d8092
9 changed files with 275 additions and 50 deletions
@@ -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, D> R accept(@NotNull JetVisitor<R, D> 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);
}
}
@@ -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()
@@ -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<KotlinSmartEnterHandler>() {
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<JetIfExpression>() {
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()
}
@@ -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<KotlinSmartEnterHandler>() {
public class KotlinMissingIfBranchFixer : SmartEnterProcessorWithFixers.Fixer<KotlinSmartEnterHandler>() {
override fun apply(editor: Editor, processor: KotlinSmartEnterHandler, element: PsiElement) {
if (element !is JetIfExpression) return
val ifExpression = element as JetIfExpression
@@ -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<KotlinSmartEnterHandler>() {
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, "{}")
}
}
@@ -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<JetWhileExpression>() {
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()
}
@@ -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<T: PsiElement>() : SmartEnterProcessorWithFixers.Fixer<KotlinSmartEnterHandler>() {
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?
}
@@ -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
@@ -266,6 +266,100 @@ class SmartEnterTest : JetLightCodeInsightFixtureTestCase() {
"""
)
fun testWhile() = doFunTest(
"""
while <caret>
"""
,
"""
while (<caret>) {
}
"""
)
fun testWhile2() = doFunTest(
"""
while<caret>
"""
,
"""
while (<caret>) {
}
"""
)
fun testWhile3() = doFunTest(
"""
while (<caret>
"""
,
"""
while (<caret>) {
}
"""
)
fun testWhile4() = doFunTest(
"""
while (true<caret>) {
}
"""
,
"""
while (true) {
<caret>
}
"""
)
fun testWhile5() = doFunTest(
"""
while (true) {<caret>
"""
,
"""
while (true) {
<caret>
}
"""
)
fun testWhile6() = doFunTest(
"""
while (true<caret>) {
println()
}
"""
,
"""
while (true) {
<caret>
println()
}
"""
)
fun testWhile7() = doFunTest(
"""
while ()<caret>
""",
"""
while (<caret>) {
}
"""
)
fun testWhileSingle() = doFunTest(
"""
<caret>while (true) println()
""",
"""
while (true) println()
<caret>
"""
)
fun doFunTest(before: String, after: String) {
fun String.withFunContext(): String {
val bodyText = "//----${this.trimIndent()}//----"