From 33de0e8393e4e73f4e71e426363033b194effd42 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Wed, 30 Apr 2014 16:17:16 +0400 Subject: [PATCH] Smart completion for then and else branches of if #KT-4910 Fixed --- .../jet/plugin/completion/ExpectedInfos.kt | 29 +++++++++- ...rtHandlers.kt => WithTailInsertHandler.kt} | 55 ++++++++++--------- .../jet/plugin/completion/smart/Utils.kt | 17 ++++-- .../completion/handlers/smart/IfCondition.kt | 5 ++ .../handlers/smart/IfCondition.kt.after | 5 ++ .../completion/handlers/smart/IfValue1.kt | 8 +++ .../handlers/smart/IfValue1.kt.after | 8 +++ .../completion/handlers/smart/IfValue2.kt | 8 +++ .../handlers/smart/IfValue2.kt.after | 8 +++ .../completion/handlers/smart/IfValue3.kt | 9 +++ .../handlers/smart/IfValue3.kt.after | 9 +++ idea/testData/completion/smart/IfCondition.kt | 6 ++ idea/testData/completion/smart/IfValue1.kt | 10 ++++ idea/testData/completion/smart/IfValue2.kt | 10 ++++ idea/testData/completion/smart/IfValue3.kt | 10 ++++ .../JvmSmartCompletionTestGenerated.java | 20 +++++++ .../SmartCompletionHandlerTestGenerated.java | 20 +++++++ 17 files changed, 203 insertions(+), 34 deletions(-) rename idea/src/org/jetbrains/jet/plugin/completion/handlers/{WithTailInsertHandlers.kt => WithTailInsertHandler.kt} (59%) create mode 100644 idea/testData/completion/handlers/smart/IfCondition.kt create mode 100644 idea/testData/completion/handlers/smart/IfCondition.kt.after create mode 100644 idea/testData/completion/handlers/smart/IfValue1.kt create mode 100644 idea/testData/completion/handlers/smart/IfValue1.kt.after create mode 100644 idea/testData/completion/handlers/smart/IfValue2.kt create mode 100644 idea/testData/completion/handlers/smart/IfValue2.kt.after create mode 100644 idea/testData/completion/handlers/smart/IfValue3.kt create mode 100644 idea/testData/completion/handlers/smart/IfValue3.kt.after create mode 100644 idea/testData/completion/smart/IfCondition.kt create mode 100644 idea/testData/completion/smart/IfValue1.kt create mode 100644 idea/testData/completion/smart/IfValue2.kt create mode 100644 idea/testData/completion/smart/IfValue3.kt diff --git a/idea/src/org/jetbrains/jet/plugin/completion/ExpectedInfos.kt b/idea/src/org/jetbrains/jet/plugin/completion/ExpectedInfos.kt index aa0000bc14c..d429bb85c53 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/ExpectedInfos.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/ExpectedInfos.kt @@ -42,19 +42,26 @@ import org.jetbrains.jet.lang.descriptors.ModuleDescriptor import org.jetbrains.jet.lang.types.JetType import org.jetbrains.jet.lang.psi.JetBinaryExpression import org.jetbrains.jet.lexer.JetTokens +import org.jetbrains.jet.lang.psi.JetIfExpression +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns +import org.jetbrains.jet.lang.psi.JetContainerNode +import org.jetbrains.jet.plugin.completion.smart.isSubtypeOf enum class Tail { COMMA PARENTHESIS + ELSE } data class ExpectedInfo(val `type`: JetType, val tail: Tail?) class ExpectedInfos(val bindingContext: BindingContext, val moduleDescriptor: ModuleDescriptor) { public fun calculate(expressionWithType: JetExpression): Collection? { - return calculateForArgument(expressionWithType) + val forArgument = calculateForArgument(expressionWithType) + return forArgument ?: calculateForFunctionLiteralArgument(expressionWithType) ?: calculateForEq(expressionWithType) + ?: calculateForIf(expressionWithType) ?: getFromBindingContext(expressionWithType) } @@ -142,6 +149,26 @@ class ExpectedInfos(val bindingContext: BindingContext, val moduleDescriptor: Mo return null } + private fun calculateForIf(expressionWithType: JetExpression): Collection? { + val ifExpression = (expressionWithType.getParent() as? JetContainerNode)?.getParent() as? JetIfExpression ?: return null + return when (expressionWithType) { + ifExpression.getCondition() -> listOf(ExpectedInfo(KotlinBuiltIns.getInstance().getBooleanType(), Tail.PARENTHESIS)) + + ifExpression.getThen() -> calculate(ifExpression)?.map { ExpectedInfo(it.`type`, Tail.ELSE) } + + ifExpression.getElse() -> { + val ifExpectedInfo = calculate(ifExpression) + val thenType = bindingContext[BindingContext.EXPRESSION_TYPE, ifExpression.getThen()] + if (thenType != null) + ifExpectedInfo?.filter { it.`type`.isSubtypeOf(thenType) } + else + ifExpectedInfo + } + + else -> return null + } + } + private fun getFromBindingContext(expressionWithType: JetExpression): Collection? { val expectedType = bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, expressionWithType] ?: return null return listOf(ExpectedInfo(expectedType, null)) diff --git a/idea/src/org/jetbrains/jet/plugin/completion/handlers/WithTailInsertHandlers.kt b/idea/src/org/jetbrains/jet/plugin/completion/handlers/WithTailInsertHandler.kt similarity index 59% rename from idea/src/org/jetbrains/jet/plugin/completion/handlers/WithTailInsertHandlers.kt rename to idea/src/org/jetbrains/jet/plugin/completion/handlers/WithTailInsertHandler.kt index ea3708b4706..e0a31a80cb4 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/handlers/WithTailInsertHandlers.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/handlers/WithTailInsertHandler.kt @@ -23,10 +23,12 @@ import com.intellij.codeInsight.AutoPopupController import com.intellij.codeInsight.lookup.Lookup import org.jetbrains.jet.plugin.completion.smart.SmartCompletion import org.jetbrains.jet.plugin.completion.smart.KEEP_OLD_ARGUMENT_LIST_ON_TAB_KEY +import com.intellij.openapi.util.TextRange -abstract class WithTailInsertHandlerBase : InsertHandler { - protected abstract fun insertTail(context: InsertionContext, offset: Int, moveCaret: Boolean) - +class WithTailInsertHandler(val tailText: String, + val spaceBefore: Boolean, + val spaceAfter: Boolean, + val overwriteText: Boolean = true) : InsertHandler { override fun handleInsert(context: InsertionContext, item: LookupElement) { val document = context.getDocument() @@ -38,40 +40,39 @@ abstract class WithTailInsertHandlerBase : InsertHandler { val offset = context.getOffsetMap().getOffset(SmartCompletion.OLD_ARGUMENTS_REPLACEMENT_OFFSET) if (offset != -1) tailOffset = offset } - insertTail(context, tailOffset, context.getEditor().getCaretModel().getOffset() == tailOffset) - } -} -class WithTailCharInsertHandler(val tailChar: Char, val spaceAfter: Boolean) : WithTailInsertHandlerBase() { - override fun insertTail(context: InsertionContext, offset: Int, moveCaret: Boolean) { - val document = context.getDocument() + val moveCaret = context.getEditor().getCaretModel().getOffset() == tailOffset + fun isCharAt(offset: Int, c: Char) = offset < document.getTextLength() && document.getCharsSequence().charAt(offset) == c + fun isTextAt(offset: Int, text: String) = offset + text.length <= document.getTextLength() && document.getText(TextRange(offset, offset + text.length)) == text - if (isCharAt(offset, tailChar)) { - document.deleteString(offset, offset + 1) + if (overwriteText) { + if (spaceBefore && isCharAt(tailOffset, ' ')) { + document.deleteString(tailOffset, tailOffset + 1) + } - if (spaceAfter && isCharAt(offset, ' ')) { - document.deleteString(offset, offset + 1) + if (isTextAt(tailOffset, tailText)) { + document.deleteString(tailOffset, tailOffset + tailText.length) + + if (spaceAfter && isCharAt(tailOffset, ' ')) { + document.deleteString(tailOffset, tailOffset + 1) + } } } - val textToInsert = if (spaceAfter) tailChar + " " else tailChar.toString() - document.insertString(offset, textToInsert) - if (moveCaret) { - context.getEditor().getCaretModel().moveToOffset(offset + textToInsert.length) - if (tailChar == ',') { + var textToInsert = tailText + if (spaceBefore) textToInsert = " " + textToInsert + if (spaceAfter) textToInsert += " " + + document.insertString(tailOffset, textToInsert) + + if (moveCaret) { + context.getEditor().getCaretModel().moveToOffset(tailOffset + textToInsert.length) + + if (tailText == ",") { AutoPopupController.getInstance(context.getProject())?.autoPopupParameterInfo(context.getEditor(), null) } } } } - -class WithTailStringInsertHandler(val tail: String) : WithTailInsertHandlerBase() { - override fun insertTail(context: InsertionContext, offset: Int, moveCaret: Boolean) { - context.getDocument().insertString(offset, tail) - if (moveCaret) { - context.getEditor().getCaretModel().moveToOffset(offset + tail.length) - } - } -} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/Utils.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/Utils.kt index 7f2303b465d..89b0b53ed5c 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/smart/Utils.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/Utils.kt @@ -24,7 +24,6 @@ import org.jetbrains.jet.lang.psi.JetFile import org.jetbrains.jet.plugin.codeInsight.ShortenReferences import java.util.HashSet import com.intellij.codeInsight.lookup.LookupElementDecorator -import org.jetbrains.jet.plugin.completion.handlers.WithTailCharInsertHandler import com.intellij.codeInsight.lookup.AutoCompletionPolicy import org.jetbrains.jet.lang.descriptors.FunctionDescriptor import org.jetbrains.jet.lang.types.JetType @@ -37,7 +36,7 @@ import com.intellij.openapi.util.Key import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor import org.jetbrains.jet.plugin.project.ResolveSessionForBodies import org.jetbrains.jet.lang.resolve.BindingContext -import org.jetbrains.jet.plugin.completion.handlers.WithTailStringInsertHandler +import org.jetbrains.jet.plugin.completion.handlers.WithTailInsertHandler class ArtificialElementInsertHandler( val textBeforeCaret: String, val textAfterCaret: String, val shortenRefs: Boolean) : InsertHandler{ @@ -70,13 +69,19 @@ fun LookupElement.addTail(tail: Tail?): LookupElement { Tail.COMMA -> object: LookupElementDecorator(this) { override fun handleInsert(context: InsertionContext) { - WithTailCharInsertHandler(',', true /*TODO: use code style option*/).handleInsert(context, getDelegate()) + WithTailInsertHandler(",", spaceBefore = false, spaceAfter = true /*TODO: use code style option*/).handleInsert(context, getDelegate()) } } Tail.PARENTHESIS -> object: LookupElementDecorator(this) { override fun handleInsert(context: InsertionContext) { - WithTailCharInsertHandler(')', false).handleInsert(context, getDelegate()) + handlers.WithTailInsertHandler(")", spaceBefore = false, spaceAfter = false).handleInsert(context, getDelegate()) + } + } + + Tail.ELSE -> object: LookupElementDecorator(this) { + override fun handleInsert(context: InsertionContext) { + handlers.WithTailInsertHandler("else", spaceBefore = true, spaceAfter = true).handleInsert(context, getDelegate()) } } } @@ -132,7 +137,7 @@ fun MutableCollection.addLookupElementsForNullable(factory: () -> presentation.setItemText("!! " + presentation.getItemText()) } override fun handleInsert(context: InsertionContext) { - WithTailStringInsertHandler("!!").handleInsert(context, getDelegate()) + WithTailInsertHandler("!!", spaceBefore = false, spaceAfter = false).handleInsert(context, getDelegate()) } } lookupElement = lookupElement!!.suppressAutoInsertion() @@ -147,7 +152,7 @@ fun MutableCollection.addLookupElementsForNullable(factory: () -> presentation.setItemText("?: " + presentation.getItemText()) } override fun handleInsert(context: InsertionContext) { - WithTailStringInsertHandler(" ?: ").handleInsert(context, getDelegate()) //TODO: code style + handlers.WithTailInsertHandler("?:", spaceBefore = true, spaceAfter = true).handleInsert(context, getDelegate()) //TODO: code style } } lookupElement = lookupElement!!.suppressAutoInsertion() diff --git a/idea/testData/completion/handlers/smart/IfCondition.kt b/idea/testData/completion/handlers/smart/IfCondition.kt new file mode 100644 index 00000000000..4dd09d7e81d --- /dev/null +++ b/idea/testData/completion/handlers/smart/IfCondition.kt @@ -0,0 +1,5 @@ +fun bar(b: Boolean){ + if ( +} + +// ELEMENT: b diff --git a/idea/testData/completion/handlers/smart/IfCondition.kt.after b/idea/testData/completion/handlers/smart/IfCondition.kt.after new file mode 100644 index 00000000000..d76d052354b --- /dev/null +++ b/idea/testData/completion/handlers/smart/IfCondition.kt.after @@ -0,0 +1,5 @@ +fun bar(b: Boolean){ + if (b) +} + +// ELEMENT: b diff --git a/idea/testData/completion/handlers/smart/IfValue1.kt b/idea/testData/completion/handlers/smart/IfValue1.kt new file mode 100644 index 00000000000..000294d7700 --- /dev/null +++ b/idea/testData/completion/handlers/smart/IfValue1.kt @@ -0,0 +1,8 @@ +fun foo(s: String, i: Int){} +fun foo(c: Char){} + +fun bar(b: Boolean, s: String){ + foo(if (b) ) +} + +// ELEMENT: s diff --git a/idea/testData/completion/handlers/smart/IfValue1.kt.after b/idea/testData/completion/handlers/smart/IfValue1.kt.after new file mode 100644 index 00000000000..1d4e27f5abd --- /dev/null +++ b/idea/testData/completion/handlers/smart/IfValue1.kt.after @@ -0,0 +1,8 @@ +fun foo(s: String, i: Int){} +fun foo(c: Char){} + +fun bar(b: Boolean, s: String){ + foo(if (b) s else ) +} + +// ELEMENT: s diff --git a/idea/testData/completion/handlers/smart/IfValue2.kt b/idea/testData/completion/handlers/smart/IfValue2.kt new file mode 100644 index 00000000000..42c36d9d245 --- /dev/null +++ b/idea/testData/completion/handlers/smart/IfValue2.kt @@ -0,0 +1,8 @@ +fun foo(s: String, i: Int){} +fun foo(c: Char){} + +fun bar(b: Boolean, s: String){ + foo(if (b) "abc" else ) +} + +// ELEMENT: s diff --git a/idea/testData/completion/handlers/smart/IfValue2.kt.after b/idea/testData/completion/handlers/smart/IfValue2.kt.after new file mode 100644 index 00000000000..27464dc052e --- /dev/null +++ b/idea/testData/completion/handlers/smart/IfValue2.kt.after @@ -0,0 +1,8 @@ +fun foo(s: String, i: Int){} +fun foo(c: Char){} + +fun bar(b: Boolean, s: String){ + foo(if (b) "abc" else s, ) +} + +// ELEMENT: s diff --git a/idea/testData/completion/handlers/smart/IfValue3.kt b/idea/testData/completion/handlers/smart/IfValue3.kt new file mode 100644 index 00000000000..e8889e6bb5f --- /dev/null +++ b/idea/testData/completion/handlers/smart/IfValue3.kt @@ -0,0 +1,9 @@ +fun foo(s: String, i: Int){} +fun foo(c: Char){} + +fun bar(b: Boolean, s: String){ + foo(if (b) xxx else "") +} + +// ELEMENT: s +// CHAR: \t diff --git a/idea/testData/completion/handlers/smart/IfValue3.kt.after b/idea/testData/completion/handlers/smart/IfValue3.kt.after new file mode 100644 index 00000000000..8919bf63b98 --- /dev/null +++ b/idea/testData/completion/handlers/smart/IfValue3.kt.after @@ -0,0 +1,9 @@ +fun foo(s: String, i: Int){} +fun foo(c: Char){} + +fun bar(b: Boolean, s: String){ + foo(if (b) s else "") +} + +// ELEMENT: s +// CHAR: \t diff --git a/idea/testData/completion/smart/IfCondition.kt b/idea/testData/completion/smart/IfCondition.kt new file mode 100644 index 00000000000..c8540debc69 --- /dev/null +++ b/idea/testData/completion/smart/IfCondition.kt @@ -0,0 +1,6 @@ +fun bar(b: Boolean, c: Char){ + if ( +} + +// EXIST: b +// ABSENT: c diff --git a/idea/testData/completion/smart/IfValue1.kt b/idea/testData/completion/smart/IfValue1.kt new file mode 100644 index 00000000000..261e447461d --- /dev/null +++ b/idea/testData/completion/smart/IfValue1.kt @@ -0,0 +1,10 @@ +fun foo(s: String){} +fun foo(c: Char){} + +fun bar(b: Boolean, s: String, c: Char){ + foo(if (b) ) +} + +// EXIST: s +// EXIST: c +// ABSENT: b diff --git a/idea/testData/completion/smart/IfValue2.kt b/idea/testData/completion/smart/IfValue2.kt new file mode 100644 index 00000000000..ce4a988fb3d --- /dev/null +++ b/idea/testData/completion/smart/IfValue2.kt @@ -0,0 +1,10 @@ +fun foo(s: String){} +fun foo(c: Char){} + +fun bar(b: Boolean, s: String, c: Char){ + foo(if (b) "abc" else ) +} + +// EXIST: s +// ABSENT: c +// ABSENT: b diff --git a/idea/testData/completion/smart/IfValue3.kt b/idea/testData/completion/smart/IfValue3.kt new file mode 100644 index 00000000000..2221e953df8 --- /dev/null +++ b/idea/testData/completion/smart/IfValue3.kt @@ -0,0 +1,10 @@ +fun foo(s: String){} +fun foo(c: Char){} + +fun bar(b: Boolean, s: String, c: Char){ + foo(if (b) xxx else ) +} + +// EXIST: s +// EXIST: c +// ABSENT: b diff --git a/idea/tests/org/jetbrains/jet/completion/JvmSmartCompletionTestGenerated.java b/idea/tests/org/jetbrains/jet/completion/JvmSmartCompletionTestGenerated.java index c7b4ada1788..731e55a9a43 100644 --- a/idea/tests/org/jetbrains/jet/completion/JvmSmartCompletionTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/completion/JvmSmartCompletionTestGenerated.java @@ -191,6 +191,26 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT doTest("idea/testData/completion/smart/FunctionReference9.kt"); } + @TestMetadata("IfCondition.kt") + public void testIfCondition() throws Exception { + doTest("idea/testData/completion/smart/IfCondition.kt"); + } + + @TestMetadata("IfValue1.kt") + public void testIfValue1() throws Exception { + doTest("idea/testData/completion/smart/IfValue1.kt"); + } + + @TestMetadata("IfValue2.kt") + public void testIfValue2() throws Exception { + doTest("idea/testData/completion/smart/IfValue2.kt"); + } + + @TestMetadata("IfValue3.kt") + public void testIfValue3() throws Exception { + doTest("idea/testData/completion/smart/IfValue3.kt"); + } + @TestMetadata("InaccessibleConstructor.kt") public void testInaccessibleConstructor() throws Exception { doTest("idea/testData/completion/smart/InaccessibleConstructor.kt"); diff --git a/idea/tests/org/jetbrains/jet/completion/handlers/SmartCompletionHandlerTestGenerated.java b/idea/tests/org/jetbrains/jet/completion/handlers/SmartCompletionHandlerTestGenerated.java index 4afa1e6bd01..a825a146c90 100644 --- a/idea/tests/org/jetbrains/jet/completion/handlers/SmartCompletionHandlerTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/completion/handlers/SmartCompletionHandlerTestGenerated.java @@ -206,6 +206,26 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion doTest("idea/testData/completion/handlers/smart/GenericFunction.kt"); } + @TestMetadata("IfCondition.kt") + public void testIfCondition() throws Exception { + doTest("idea/testData/completion/handlers/smart/IfCondition.kt"); + } + + @TestMetadata("IfValue1.kt") + public void testIfValue1() throws Exception { + doTest("idea/testData/completion/handlers/smart/IfValue1.kt"); + } + + @TestMetadata("IfValue2.kt") + public void testIfValue2() throws Exception { + doTest("idea/testData/completion/handlers/smart/IfValue2.kt"); + } + + @TestMetadata("IfValue3.kt") + public void testIfValue3() throws Exception { + doTest("idea/testData/completion/handlers/smart/IfValue3.kt"); + } + @TestMetadata("JavaEnumMemberInsertsImport.kt") public void testJavaEnumMemberInsertsImport() throws Exception { doTest("idea/testData/completion/handlers/smart/JavaEnumMemberInsertsImport.kt");