diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/generateUtil.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/generateUtil.kt index 4cda16f0873..0f83817f9f7 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/generateUtil.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/generateUtil.kt @@ -17,16 +17,27 @@ package org.jetbrains.kotlin.idea.quickfix import com.intellij.openapi.editor.Editor -import com.intellij.psi.PsiElement -import org.jetbrains.kotlin.psi.JetDeclarationWithBody -import org.jetbrains.kotlin.psi.JetBlockExpression -import com.intellij.psi.PsiWhiteSpace import com.intellij.openapi.editor.ScrollType -import org.jetbrains.kotlin.psi.JetWithExpressionInitializer +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.SmartPointerManager +import org.jetbrains.kotlin.psi.JetBlockExpression +import org.jetbrains.kotlin.psi.JetDeclarationWithBody import org.jetbrains.kotlin.psi.JetProperty +import org.jetbrains.kotlin.psi.JetWithExpressionInitializer import org.jetbrains.kotlin.psi.psiUtil.siblings -public fun moveCaretIntoGeneratedElement(editor: Editor, element: PsiElement): Boolean { +public fun moveCaretIntoGeneratedElement(editor: Editor, element: PsiElement) { + val project = element.project + val pointer = SmartPointerManager.getInstance(project).createSmartPsiElementPointer(element) + + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.document) + + pointer.element?.let { moveCaretIntoGeneratedElementDocumentUnblocked(editor, it) } +} + +private fun moveCaretIntoGeneratedElementDocumentUnblocked(editor: Editor, element: PsiElement): Boolean { // Inspired by GenerateMembersUtils.positionCaret() if (element is JetDeclarationWithBody && element.hasBody()) { @@ -72,7 +83,7 @@ public fun moveCaretIntoGeneratedElement(editor: Editor, element: PsiElement): B if (element is JetProperty) { for (accessor in element.getAccessors()) { - if (moveCaretIntoGeneratedElement(editor, accessor)) { + if (moveCaretIntoGeneratedElementDocumentUnblocked(editor, accessor)) { return true } } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt index 3d35aff3f7b..299ed81ccb9 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt @@ -257,6 +257,12 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, } } + "override" -> { + collector.addElement(lookupElement) + + OverridesCompletion(collector, lookupElementFactory).complete(position) + } + else -> collector.addElement(lookupElement) } } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt index 274dc6e51ac..b42ae6bd574 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt @@ -395,7 +395,7 @@ public class KotlinCompletionContributor : CompletionContributor() { if (parent is JetParameterList) { val balance = countParenthesisBalance(tokenBefore, parent) val count = if (balance > 1) balance - 1 else 0 - return CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + ")".repeat(count) + " a: B$" + return CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + ")".repeat(count) + " a: B" } if (parent is JetTypeElement) return null if (parent is JetAnnotationEntry) return null diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/OverridesCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/OverridesCompletion.kt new file mode 100644 index 00000000000..46af121bbd1 --- /dev/null +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/OverridesCompletion.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2010-2015 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.kotlin.idea.completion + +import com.intellij.codeInsight.completion.InsertionContext +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupElementDecorator +import com.intellij.codeInsight.lookup.LookupElementPresentation +import com.intellij.icons.AllIcons +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.ui.RowIcon +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.idea.JetDescriptorIconProvider +import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde +import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject +import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMembersHandler +import org.jetbrains.kotlin.idea.core.overrideImplement.generateMember +import org.jetbrains.kotlin.idea.core.replaced +import org.jetbrains.kotlin.idea.quickfix.moveCaret +import org.jetbrains.kotlin.idea.quickfix.moveCaretIntoGeneratedElement +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.idea.util.ShortenReferences +import org.jetbrains.kotlin.psi.JetClassOrObject +import org.jetbrains.kotlin.psi.JetNamedDeclaration +import org.jetbrains.kotlin.psi.JetPrimaryConstructor +import org.jetbrains.kotlin.psi.JetPsiFactory +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType + +class OverridesCompletion( + private val collector: LookupElementsCollector, + private val lookupElementFactory: LookupElementFactory +) { + private val PRESENTATION_RENDERER = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.withOptions { + modifiers = emptySet() + } + + fun complete(position: PsiElement) { + val isConstructorParameter = position.getNonStrictParentOfType() != null + + val classOrObject = position.getNonStrictParentOfType() ?: return + + val members = OverrideMembersHandler().collectMembersToGenerate(classOrObject) + + for (memberObject in members) { + if (isConstructorParameter && memberObject.descriptor !is PropertyDescriptor) continue + + val descriptor = memberObject.descriptor + var lookupElement = lookupElementFactory.createLookupElement(descriptor, useReceiverTypes = false) + + var text = "override " + PRESENTATION_RENDERER.render(descriptor) + if (descriptor is FunctionDescriptor) { + text += " {...}" + } + + val baseClass = descriptor.containingDeclaration as ClassDescriptor + val baseClassName = baseClass.name.asString() + + val baseIcon = (lookupElement.`object` as DeclarationLookupObject).getIcon(0) + val additionalIcon = if (descriptor.modality == Modality.ABSTRACT) + AllIcons.Gutter.ImplementingMethod + else + AllIcons.Gutter.OverridingMethod + val icon = RowIcon(baseIcon, additionalIcon) + + val baseClassDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(position.project, baseClass) + val baseClassIcon = JetDescriptorIconProvider.getIcon(baseClass, baseClassDeclaration, 0) + + lookupElement = object : LookupElementDecorator(lookupElement) { + override fun getLookupString() = "override" + override fun getAllLookupStrings() = setOf(lookupString) + + override fun renderElement(presentation: LookupElementPresentation) { + super.renderElement(presentation) + + presentation.itemText = text + presentation.icon = icon + presentation.clearTail() + presentation.setTypeText(baseClassName, baseClassIcon) + } + + override fun handleInsert(context: InsertionContext) { + val dummyMemberText = if (isConstructorParameter) "override val dummy" else "override fun dummy() {}" + context.document.replaceString(context.startOffset, context.tailOffset, dummyMemberText) + + PsiDocumentManager.getInstance(context.project).commitAllDocuments() + + val dummyMember = context.file.findElementAt(context.startOffset)!!.getStrictParentOfType()!! + + // keep original modifiers + val modifierList = JetPsiFactory(context.project).createModifierList(dummyMember.modifierList!!.text) + + val prototype = memberObject.generateMember(context.project, isConstructorParameter) + prototype.modifierList!!.replace(modifierList) + val insertedMember = dummyMember.replaced(prototype) + + ShortenReferences.DEFAULT.process(insertedMember) + + if (isConstructorParameter) { + context.editor.moveCaret(insertedMember.endOffset) + } + else { + moveCaretIntoGeneratedElement(context.editor, insertedMember) + } + } + } + + collector.addElement(lookupElement) + } + } +} \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/override/Generics.kt b/idea/idea-completion/testData/basic/common/override/Generics.kt new file mode 100644 index 00000000000..e8112cf5f6a --- /dev/null +++ b/idea/idea-completion/testData/basic/common/override/Generics.kt @@ -0,0 +1,12 @@ +interface I { + fun foo(t: T): T +} + +class A : List, I { + o +} + +// EXIST: { lookupString: "override", itemText: "override" } +// EXIST: { itemText: "override fun hashCode(): Int {...}", tailText: null, typeText: "Any" } +// EXIST: { itemText: "override fun foo(t: T): T {...}", tailText: null, typeText: "I" } +// EXIST: { itemText: "override fun get(index: Int): String {...}", tailText: null, typeText: "List" } diff --git a/idea/idea-completion/testData/basic/common/override/InConstructorParameters.kt b/idea/idea-completion/testData/basic/common/override/InConstructorParameters.kt new file mode 100644 index 00000000000..319b224a62e --- /dev/null +++ b/idea/idea-completion/testData/basic/common/override/InConstructorParameters.kt @@ -0,0 +1,22 @@ +interface I { + fun foo() + val someVal: Int + var someVar: Int +} + +class Base1 { + protected open fun bar(){} + open val fromBase: String = "" +} + +open class Base2 : Base1() { +} + +class A(overr) : Base2(), I + +// EXIST: { lookupString: "override", itemText: "override" } +// EXIST: { itemText: "override val someVal: Int", tailText: null, typeText: "I" } +// EXIST: { itemText: "override var someVar: Int", tailText: null, typeText: "I" } +// EXIST: { itemText: "override val fromBase: String", tailText: null, typeText: "Base1" } +// EXIST_JAVA_ONLY: { lookupString: "override", itemText: "override: Override" } +// NOTHING_ELSE diff --git a/idea/idea-completion/testData/basic/common/override/Simple.kt b/idea/idea-completion/testData/basic/common/override/Simple.kt new file mode 100644 index 00000000000..733fa400c7a --- /dev/null +++ b/idea/idea-completion/testData/basic/common/override/Simple.kt @@ -0,0 +1,24 @@ +interface I { + fun foo() + val someVal: Int + var someVar: Int +} + +class Base1 { + protected open fun bar(){} +} + +open class Base2 : Base1() { +} + +class A : Base2(), I { + o +} + +// EXIST: { lookupString: "override", itemText: "override" } +// EXIST: { itemText: "override fun bar() {...}", lookupString: "override", tailText: null, typeText: "Base1" } +// EXIST: { itemText: "override fun equals(other: Any?): Boolean {...}", lookupString: "override", tailText: null, typeText: "Any" } +// EXIST: { itemText: "override fun foo() {...}", lookupString: "override", tailText: null, typeText: "I" } +// EXIST: { itemText: "override fun hashCode(): Int {...}", lookupString: "override", tailText: null, typeText: "Any" } +// EXIST: { itemText: "override val someVal: Int", lookupString: "override", tailText: null, typeText: "I" } +// EXIST: { itemText: "override var someVar: Int", lookupString: "override", tailText: null, typeText: "I" } diff --git a/idea/idea-completion/testData/handlers/basic/override/ImplementFunction.kt b/idea/idea-completion/testData/handlers/basic/override/ImplementFunction.kt new file mode 100644 index 00000000000..e3acc578ce6 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/ImplementFunction.kt @@ -0,0 +1,9 @@ +interface I { + fun foo(p: Int) +} + +class A : I { + o +} + +// ELEMENT_TEXT: "override fun foo(p: Int) {...}" diff --git a/idea/idea-completion/testData/handlers/basic/override/ImplementFunction.kt.after b/idea/idea-completion/testData/handlers/basic/override/ImplementFunction.kt.after new file mode 100644 index 00000000000..07ca0c5164e --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/ImplementFunction.kt.after @@ -0,0 +1,11 @@ +interface I { + fun foo(p: Int) +} + +class A : I { + override fun foo(p: Int) { + throw UnsupportedOperationException() + } +} + +// ELEMENT_TEXT: "override fun foo(p: Int) {...}" diff --git a/idea/idea-completion/testData/handlers/basic/override/ImplementVal.kt b/idea/idea-completion/testData/handlers/basic/override/ImplementVal.kt new file mode 100644 index 00000000000..ab3435662fc --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/ImplementVal.kt @@ -0,0 +1,9 @@ +interface I { + val someVal: String? +} + +class A : I { + o +} + +// ELEMENT_TEXT: "override val someVal: String?" diff --git a/idea/idea-completion/testData/handlers/basic/override/ImplementVal.kt.after b/idea/idea-completion/testData/handlers/basic/override/ImplementVal.kt.after new file mode 100644 index 00000000000..14b57b1755e --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/ImplementVal.kt.after @@ -0,0 +1,10 @@ +interface I { + val someVal: String? +} + +class A : I { + override val someVal: String? + get() = throw UnsupportedOperationException() +} + +// ELEMENT_TEXT: "override val someVal: String?" diff --git a/idea/idea-completion/testData/handlers/basic/override/ImplementVar.kt b/idea/idea-completion/testData/handlers/basic/override/ImplementVar.kt new file mode 100644 index 00000000000..7819a8a7ab9 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/ImplementVar.kt @@ -0,0 +1,9 @@ +interface I { + var someVar: String +} + +class A : I { + o +} + +// ELEMENT_TEXT: "override var someVar: String" diff --git a/idea/idea-completion/testData/handlers/basic/override/ImplementVar.kt.after b/idea/idea-completion/testData/handlers/basic/override/ImplementVar.kt.after new file mode 100644 index 00000000000..a9f35a12854 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/ImplementVar.kt.after @@ -0,0 +1,12 @@ +interface I { + var someVar: String +} + +class A : I { + override var someVar: String + get() = throw UnsupportedOperationException() + set(value) { + } +} + +// ELEMENT_TEXT: "override var someVar: String" diff --git a/idea/idea-completion/testData/handlers/basic/override/KeepAnnotationBefore.kt b/idea/idea-completion/testData/handlers/basic/override/KeepAnnotationBefore.kt new file mode 100644 index 00000000000..2f32113e657 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/KeepAnnotationBefore.kt @@ -0,0 +1,6 @@ +class A { + @Deprecated("") + o +} + +// ELEMENT_TEXT: "override fun equals(other: Any?): Boolean {...}" diff --git a/idea/idea-completion/testData/handlers/basic/override/KeepAnnotationBefore.kt.after b/idea/idea-completion/testData/handlers/basic/override/KeepAnnotationBefore.kt.after new file mode 100644 index 00000000000..f34c4cbfc83 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/KeepAnnotationBefore.kt.after @@ -0,0 +1,8 @@ +class A { + @Deprecated("") + override fun equals(other: Any?): Boolean { + return super.equals(other) + } +} + +// ELEMENT_TEXT: "override fun equals(other: Any?): Boolean {...}" diff --git a/idea/idea-completion/testData/handlers/basic/override/KeepModifiersBefore.kt b/idea/idea-completion/testData/handlers/basic/override/KeepModifiersBefore.kt new file mode 100644 index 00000000000..02383882320 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/KeepModifiersBefore.kt @@ -0,0 +1,6 @@ +class A { + @Deprecated("") // it is deprecated + public o +} + +// ELEMENT_TEXT: "override fun equals(other: Any?): Boolean {...}" diff --git a/idea/idea-completion/testData/handlers/basic/override/KeepModifiersBefore.kt.after b/idea/idea-completion/testData/handlers/basic/override/KeepModifiersBefore.kt.after new file mode 100644 index 00000000000..055f2fe7a4b --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/KeepModifiersBefore.kt.after @@ -0,0 +1,8 @@ +class A { + @Deprecated("") // it is deprecated + public override fun equals(other: Any?): Boolean { + return super.equals(other) + } +} + +// ELEMENT_TEXT: "override fun equals(other: Any?): Boolean {...}" diff --git a/idea/idea-completion/testData/handlers/basic/override/OverrideFunction.kt b/idea/idea-completion/testData/handlers/basic/override/OverrideFunction.kt new file mode 100644 index 00000000000..94523fa7266 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/OverrideFunction.kt @@ -0,0 +1,5 @@ +class A { + o +} + +// ELEMENT_TEXT: "override fun equals(other: Any?): Boolean {...}" diff --git a/idea/idea-completion/testData/handlers/basic/override/OverrideFunction.kt.after b/idea/idea-completion/testData/handlers/basic/override/OverrideFunction.kt.after new file mode 100644 index 00000000000..8938f4d0427 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/OverrideFunction.kt.after @@ -0,0 +1,7 @@ +class A { + override fun equals(other: Any?): Boolean { + return super.equals(other) + } +} + +// ELEMENT_TEXT: "override fun equals(other: Any?): Boolean {...}" diff --git a/idea/idea-completion/testData/handlers/basic/override/OverrideVar.kt b/idea/idea-completion/testData/handlers/basic/override/OverrideVar.kt new file mode 100644 index 00000000000..f061cd35434 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/OverrideVar.kt @@ -0,0 +1,9 @@ +open class B { + open var someVar: String = "" +} + +class A : B { + o +} + +// ELEMENT_TEXT: "override var someVar: String" diff --git a/idea/idea-completion/testData/handlers/basic/override/OverrideVar.kt.after b/idea/idea-completion/testData/handlers/basic/override/OverrideVar.kt.after new file mode 100644 index 00000000000..1fab662cb47 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/OverrideVar.kt.after @@ -0,0 +1,12 @@ +open class B { + open var someVar: String = "" +} + +class A : B { + override var someVar: String + get() = super.someVar + set(value) { + } +} + +// ELEMENT_TEXT: "override var someVar: String" diff --git a/idea/idea-completion/testData/handlers/basic/override/PublicValInConstructorParameter.kt b/idea/idea-completion/testData/handlers/basic/override/PublicValInConstructorParameter.kt new file mode 100644 index 00000000000..b86ef98a962 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/PublicValInConstructorParameter.kt @@ -0,0 +1,8 @@ +interface I { + val someVal: java.io.File? +} + +class A(public ov) : I { +} + +// ELEMENT_TEXT: "override val someVal: File?" diff --git a/idea/idea-completion/testData/handlers/basic/override/PublicValInConstructorParameter.kt.after b/idea/idea-completion/testData/handlers/basic/override/PublicValInConstructorParameter.kt.after new file mode 100644 index 00000000000..55d35d8260b --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/PublicValInConstructorParameter.kt.after @@ -0,0 +1,10 @@ +import java.io.File + +interface I { + val someVal: java.io.File? +} + +class A(public override val someVal: File?) : I { +} + +// ELEMENT_TEXT: "override val someVal: File?" diff --git a/idea/idea-completion/testData/handlers/basic/override/ValInConstructorParameter.kt b/idea/idea-completion/testData/handlers/basic/override/ValInConstructorParameter.kt new file mode 100644 index 00000000000..8173abf2740 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/ValInConstructorParameter.kt @@ -0,0 +1,8 @@ +interface I { + val someVal: java.io.File? +} + +class A(ov) : I { +} + +// ELEMENT_TEXT: "override val someVal: File?" diff --git a/idea/idea-completion/testData/handlers/basic/override/ValInConstructorParameter.kt.after b/idea/idea-completion/testData/handlers/basic/override/ValInConstructorParameter.kt.after new file mode 100644 index 00000000000..d341a84ed25 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/override/ValInConstructorParameter.kt.after @@ -0,0 +1,10 @@ +import java.io.File + +interface I { + val someVal: java.io.File? +} + +class A(override val someVal: File?) : I { +} + +// ELEMENT_TEXT: "override val someVal: File?" diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java index f16ea246cd8..5b5554c6459 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java @@ -1591,6 +1591,33 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes } } + @TestMetadata("idea/idea-completion/testData/basic/common/override") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Override extends AbstractJSBasicCompletionTest { + public void testAllFilesPresentInOverride() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/override"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("Generics.kt") + public void testGenerics() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/Generics.kt"); + doTest(fileName); + } + + @TestMetadata("InConstructorParameters.kt") + public void testInConstructorParameters() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/InConstructorParameters.kt"); + doTest(fileName); + } + + @TestMetadata("Simple.kt") + public void testSimple() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/Simple.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/idea-completion/testData/basic/common/parameterNameAndType") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java index 4e99d14750b..b93edf71034 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java @@ -1591,6 +1591,33 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT } } + @TestMetadata("idea/idea-completion/testData/basic/common/override") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Override extends AbstractJvmBasicCompletionTest { + public void testAllFilesPresentInOverride() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/override"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("Generics.kt") + public void testGenerics() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/Generics.kt"); + doTest(fileName); + } + + @TestMetadata("InConstructorParameters.kt") + public void testInConstructorParameters() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/InConstructorParameters.kt"); + doTest(fileName); + } + + @TestMetadata("Simple.kt") + public void testSimple() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/Simple.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/idea-completion/testData/basic/common/parameterNameAndType") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java index d2d12db39c6..ef404cdf8a6 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java @@ -311,6 +311,69 @@ public class BasicCompletionHandlerTestGenerated extends AbstractBasicCompletion } } + @TestMetadata("idea/idea-completion/testData/handlers/basic/override") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Override extends AbstractBasicCompletionHandlerTest { + public void testAllFilesPresentInOverride() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/handlers/basic/override"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("ImplementFunction.kt") + public void testImplementFunction() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/override/ImplementFunction.kt"); + doTest(fileName); + } + + @TestMetadata("ImplementVal.kt") + public void testImplementVal() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/override/ImplementVal.kt"); + doTest(fileName); + } + + @TestMetadata("ImplementVar.kt") + public void testImplementVar() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/override/ImplementVar.kt"); + doTest(fileName); + } + + @TestMetadata("KeepAnnotationBefore.kt") + public void testKeepAnnotationBefore() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/override/KeepAnnotationBefore.kt"); + doTest(fileName); + } + + @TestMetadata("KeepModifiersBefore.kt") + public void testKeepModifiersBefore() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/override/KeepModifiersBefore.kt"); + doTest(fileName); + } + + @TestMetadata("OverrideFunction.kt") + public void testOverrideFunction() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/override/OverrideFunction.kt"); + doTest(fileName); + } + + @TestMetadata("OverrideVar.kt") + public void testOverrideVar() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/override/OverrideVar.kt"); + doTest(fileName); + } + + @TestMetadata("PublicValInConstructorParameter.kt") + public void testPublicValInConstructorParameter() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/override/PublicValInConstructorParameter.kt"); + doTest(fileName); + } + + @TestMetadata("ValInConstructorParameter.kt") + public void testValInConstructorParameter() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/override/ValInConstructorParameter.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/idea-completion/testData/handlers/basic/parameterNameAndType") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideImplementMembersHandler.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideImplementMembersHandler.kt index b1f36b5b066..165f8d077cc 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideImplementMembersHandler.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideImplementMembersHandler.kt @@ -23,18 +23,18 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.util.text.StringUtil -import com.intellij.psi.* +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.util.PsiTreeUtil -import org.jetbrains.kotlin.builtins.KotlinBuiltIns -import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.quickfix.moveCaretIntoGeneratedElement -import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.ShortenReferences import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType -import org.jetbrains.kotlin.renderer.* import java.util.* public abstract class OverrideImplementMembersHandler : LanguageCodeInsightActionHandler { @@ -86,7 +86,7 @@ public abstract class OverrideImplementMembersHandler : LanguageCodeInsightActio PsiDocumentManager.getInstance(project).commitAllDocuments() - generateMethods(editor, classOrObject, selectedElements) + generateMembers(editor, classOrObject, selectedElements) } override fun invoke(project: Project, editor: Editor, file: PsiFile) = invoke(project, editor, file, false) @@ -94,50 +94,33 @@ public abstract class OverrideImplementMembersHandler : LanguageCodeInsightActio override fun startInWriteAction(): Boolean = false companion object { - private val OVERRIDE_RENDERER = DescriptorRenderer.withOptions { - renderDefaultValues = false - modifiers = setOf(DescriptorRendererModifier.OVERRIDE) - withDefinedIn = false - nameShortness = NameShortness.SOURCE_CODE_QUALIFIED - overrideRenderingPolicy = OverrideRenderingPolicy.RENDER_OVERRIDE - unitReturnType = false - typeNormalizer = IdeDescriptorRenderers.APPROXIMATE_FLEXIBLE_TYPES - } - - public fun generateMethods(editor: Editor, classOrObject: JetClassOrObject, selectedElements: Collection) { + public fun generateMembers(editor: Editor, classOrObject: JetClassOrObject, selectedElements: Collection) { runWriteAction { + if (selectedElements.isEmpty()) return@runWriteAction + val body = classOrObject.getOrCreateBody() var afterAnchor = findInsertAfterAnchor(editor, body) ?: return@runWriteAction var firstGenerated: PsiElement? = null - val elementsToCompact = ArrayList() - for (element in generateOverridingMembers(selectedElements, classOrObject)) { - val added = body.addAfter(element, afterAnchor) + val project = classOrObject.project + val insertedMembers = ArrayList() + for (memberObject in selectedElements) { + val member = memberObject.generateMember(project) + val added = body.addAfter(member, afterAnchor) as JetCallableDeclaration if (firstGenerated == null) { firstGenerated = added } afterAnchor = added - elementsToCompact.add(added as JetElement) + insertedMembers.add(added) } - ShortenReferences.Companion.DEFAULT.process(elementsToCompact) - - if (firstGenerated == null) return@runWriteAction - - val project = classOrObject.project - val pointer = SmartPointerManager.getInstance(project).createSmartPsiElementPointer(firstGenerated) - - PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.document) - - val element = pointer.element - if (element != null) { - moveCaretIntoGeneratedElement(editor, element) - } + ShortenReferences.DEFAULT.process(insertedMembers) + moveCaretIntoGeneratedElement(editor, firstGenerated!!) } } @@ -182,76 +165,5 @@ public abstract class OverrideImplementMembersHandler : LanguageCodeInsightActio return whiteSpace } - - private fun generateOverridingMembers(selectedElements: Collection, - classOrObject: JetClassOrObject): List { - val overridingMembers = ArrayList() - for (selectedElement in selectedElements) { - val descriptor = selectedElement.immediateSuper - when (descriptor) { - is SimpleFunctionDescriptor -> overridingMembers.add(overrideFunction(classOrObject, descriptor, selectedElement.bodyType)) - is PropertyDescriptor -> overridingMembers.add(overrideProperty(classOrObject, descriptor, selectedElement.bodyType)) - else -> error("Unknown member to override: $descriptor") - } - } - return overridingMembers - } - - private fun overrideProperty(classOrObject: JetClassOrObject, descriptor: PropertyDescriptor, bodyType: OverrideMemberChooserObject.BodyType): JetElement { - val newDescriptor = descriptor.copy(descriptor.containingDeclaration, Modality.OPEN, descriptor.visibility, - descriptor.kind, /* copyOverrides = */ true) as PropertyDescriptor - newDescriptor.addOverriddenDescriptor(descriptor) - - val body = StringBuilder { - append("\nget()") - append(" = ") - append(generateUnsupportedOrSuperCall(descriptor, bodyType)) - if (descriptor.isVar) { - append("\nset(value) {}") - } - } - return JetPsiFactory(classOrObject.project).createProperty(OVERRIDE_RENDERER.render(newDescriptor) + body) - } - - private fun overrideFunction(classOrObject: JetClassOrObject, descriptor: FunctionDescriptor, bodyType: OverrideMemberChooserObject.BodyType): JetNamedFunction { - val newDescriptor = descriptor.copy(descriptor.containingDeclaration, Modality.OPEN, descriptor.visibility, - descriptor.kind, /* copyOverrides = */ true) - newDescriptor.addOverriddenDescriptor(descriptor) - - val returnType = descriptor.returnType - val returnsNotUnit = returnType != null && !KotlinBuiltIns.isUnit(returnType) - - val delegation = generateUnsupportedOrSuperCall(descriptor, bodyType) - - val body = "{" + (if (returnsNotUnit && bodyType != OverrideMemberChooserObject.BodyType.EMPTY) "return " else "") + delegation + "}" - - return JetPsiFactory(classOrObject.project).createFunction(OVERRIDE_RENDERER.render(newDescriptor) + body) - } - - private fun generateUnsupportedOrSuperCall(descriptor: CallableMemberDescriptor, bodyType: OverrideMemberChooserObject.BodyType): String { - if (bodyType == OverrideMemberChooserObject.BodyType.EMPTY) { - return "throw UnsupportedOperationException()" - } - else { - return StringBuilder { - append("super") - if (bodyType == OverrideMemberChooserObject.BodyType.QUALIFIED_SUPER) { - val superClassFqName = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(descriptor.containingDeclaration as ClassifierDescriptor) - append("<").append(superClassFqName).append(">") - } - append(".").append(descriptor.escapedName()) - - if (descriptor is FunctionDescriptor) { - val paramTexts = descriptor.valueParameters.map { - val renderedName = it.escapedName() - if (it.varargElementType != null) "*$renderedName" else renderedName - } - paramTexts.joinTo(this, prefix="(", postfix=")") - } - }.toString() - } - } - - private fun DeclarationDescriptor.escapedName() = name.render() } } diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMemberChooserObject.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMemberChooserObject.kt index 521fea8fdc5..a28a4c1f250 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMemberChooserObject.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMemberChooserObject.kt @@ -21,10 +21,13 @@ import com.intellij.codeInsight.generation.MemberChooserObject import com.intellij.codeInsight.generation.MemberChooserObjectBase import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement -import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor -import org.jetbrains.kotlin.descriptors.ClassifierDescriptor +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.core.util.DescriptorMemberChooserObject +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.renderer.* interface OverrideMemberChooserObject : ClassMember { enum class BodyType { @@ -56,7 +59,7 @@ interface OverrideMemberChooserObject : ClassMember { ) : DescriptorMemberChooserObject(declaration, descriptor), OverrideMemberChooserObject { override val descriptor: CallableMemberDescriptor - get() = super.descriptor as CallableMemberDescriptor + get() = super.descriptor as CallableMemberDescriptor } private class WithoutDeclaration( @@ -72,3 +75,91 @@ interface OverrideMemberChooserObject : ClassMember { } } } + +fun OverrideMemberChooserObject.generateMember(project: Project, asConstructorParameter: Boolean = false): JetCallableDeclaration { + val descriptor = immediateSuper + if (asConstructorParameter) { + assert(descriptor is PropertyDescriptor) { "asConstructorParameter is valid only for PropertyDescriptor" } + return generateConstructorParameter(project, descriptor as PropertyDescriptor) + } + + return when (descriptor) { + is SimpleFunctionDescriptor -> generateFunction(project, descriptor, bodyType) + is PropertyDescriptor -> generateProperty(project, descriptor, bodyType) + else -> error("Unknown member to override: $descriptor") + } +} + +private val OVERRIDE_RENDERER = DescriptorRenderer.withOptions { + renderDefaultValues = false + modifiers = setOf(DescriptorRendererModifier.OVERRIDE) + withDefinedIn = false + nameShortness = NameShortness.SOURCE_CODE_QUALIFIED + overrideRenderingPolicy = OverrideRenderingPolicy.RENDER_OVERRIDE + unitReturnType = false + typeNormalizer = IdeDescriptorRenderers.APPROXIMATE_FLEXIBLE_TYPES +} + +private fun generateProperty(project: Project, descriptor: PropertyDescriptor, bodyType: OverrideMemberChooserObject.BodyType): JetProperty { + val newDescriptor = descriptor.copy(descriptor.containingDeclaration, Modality.OPEN, descriptor.visibility, + descriptor.kind, /* copyOverrides = */ true) as PropertyDescriptor + newDescriptor.addOverriddenDescriptor(descriptor) + + val body = StringBuilder { + append("\nget()") + append(" = ") + append(generateUnsupportedOrSuperCall(descriptor, bodyType)) + if (descriptor.isVar) { + append("\nset(value) {}") + } + } + return JetPsiFactory(project).createProperty(OVERRIDE_RENDERER.render(newDescriptor) + body) +} + +private fun generateConstructorParameter(project: Project, descriptor: PropertyDescriptor): JetParameter { + val newDescriptor = descriptor.copy(descriptor.containingDeclaration, Modality.OPEN, descriptor.visibility, + descriptor.kind, /* copyOverrides = */ true) as PropertyDescriptor + newDescriptor.addOverriddenDescriptor(descriptor) + return JetPsiFactory(project).createParameter(OVERRIDE_RENDERER.render(newDescriptor)) +} + +private fun generateFunction(project: Project, descriptor: FunctionDescriptor, bodyType: OverrideMemberChooserObject.BodyType): JetNamedFunction { + val newDescriptor = descriptor.copy(descriptor.containingDeclaration, Modality.OPEN, descriptor.visibility, + descriptor.kind, /* copyOverrides = */ true) + newDescriptor.addOverriddenDescriptor(descriptor) + + val returnType = descriptor.returnType + val returnsNotUnit = returnType != null && !KotlinBuiltIns.isUnit(returnType) + + val delegation = generateUnsupportedOrSuperCall(descriptor, bodyType) + + val body = "{" + (if (returnsNotUnit && bodyType != OverrideMemberChooserObject.BodyType.EMPTY) "return " else "") + delegation + "}" + + return JetPsiFactory(project).createFunction(OVERRIDE_RENDERER.render(newDescriptor) + body) +} + +private fun generateUnsupportedOrSuperCall(descriptor: CallableMemberDescriptor, bodyType: OverrideMemberChooserObject.BodyType): String { + if (bodyType == OverrideMemberChooserObject.BodyType.EMPTY) { + return "throw UnsupportedOperationException()" + } + else { + return StringBuilder { + append("super") + if (bodyType == OverrideMemberChooserObject.BodyType.QUALIFIED_SUPER) { + val superClassFqName = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(descriptor.containingDeclaration as ClassifierDescriptor) + append("<").append(superClassFqName).append(">") + } + append(".").append(descriptor.name.render()) + + if (descriptor is FunctionDescriptor) { + val paramTexts = descriptor.valueParameters.map { + val renderedName = it.name.render() + if (it.varargElementType != null) "*$renderedName" else renderedName + } + paramTexts.joinTo(this, prefix="(", postfix=")") + } + }.toString() + } +} + + diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractOverrideImplementTest.kt b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractOverrideImplementTest.kt index 7d5fcf5b4c1..035dbeae958 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractOverrideImplementTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractOverrideImplementTest.kt @@ -176,7 +176,7 @@ public abstract class AbstractOverrideImplementTest : JetLightCodeInsightFixture selectedElements: List) { try { myFixture.project.executeWriteCommand("") { - OverrideImplementMembersHandler.generateMethods(myFixture.editor, classOrObject, selectedElements) + OverrideImplementMembersHandler.generateMembers(myFixture.editor, classOrObject, selectedElements) } } catch (throwable: Throwable) {