KT-13298 Suggest names for overridden properties / functions in completion

#KT-13298 Fixed
This commit is contained in:
Valentin Kipyatkov
2016-08-15 19:17:33 +03:00
parent 7ebf001d25
commit 7f07890f6a
20 changed files with 319 additions and 33 deletions
@@ -174,7 +174,7 @@ class BasicCompletionSession(
override fun doComplete() {
val declaration = isStartOfExtensionReceiverFor()
if (declaration != null) {
completeDeclarationNameFromUnresolved(declaration)
completeDeclarationNameFromUnresolvedOrOverride(declaration)
// no auto-popup on typing after "val", "var" and "fun" because it's likely the name of the declaration which is being typed by user
if (parameters.invocationCount == 0) {
@@ -379,7 +379,7 @@ class BasicCompletionSession(
"override" -> {
collector.addElement(lookupElement)
OverridesCompletion(collector, basicLookupElementFactory).complete(position)
OverridesCompletion(collector, basicLookupElementFactory).complete(position, declaration = null)
}
"class" -> {
@@ -436,7 +436,7 @@ class BasicCompletionSession(
KEYWORDS_ONLY.doComplete()
completeDeclarationNameFromUnresolved(declaration)
completeDeclarationNameFromUnresolvedOrOverride(declaration)
when (declaration) {
is KtParameter ->
@@ -536,12 +536,17 @@ class BasicCompletionSession(
}
}
private fun completeDeclarationNameFromUnresolved(declaration: KtNamedDeclaration) {
val referenceScope = referenceScope(declaration) ?: return
val originalScope = toFromOriginalFileMapper.toOriginalFile(referenceScope) ?: return
val afterOffset = if (referenceScope is KtBlockExpression) parameters.offset else null
val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration]
FromUnresolvedNamesCompletion(collector, prefixMatcher).addNameSuggestions(originalScope, afterOffset, descriptor)
private fun completeDeclarationNameFromUnresolvedOrOverride(declaration: KtNamedDeclaration) {
if (declaration is KtCallableDeclaration && declaration.hasModifier(KtTokens.OVERRIDE_KEYWORD)) {
OverridesCompletion(collector, basicLookupElementFactory).complete(position, declaration)
}
else {
val referenceScope = referenceScope(declaration) ?: return
val originalScope = toFromOriginalFileMapper.toOriginalFile(referenceScope) ?: return
val afterOffset = if (referenceScope is KtBlockExpression) parameters.offset else null
val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration]
FromUnresolvedNamesCompletion(collector, prefixMatcher).addNameSuggestions(originalScope, afterOffset, descriptor)
}
}
private fun referenceScope(declaration: KtNamedDeclaration): KtElement? {
@@ -24,25 +24,20 @@ 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.descriptors.*
import org.jetbrains.kotlin.idea.KotlinDescriptorIconProvider
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.completion.handlers.indexOfSkippingSpace
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject
import org.jetbrains.kotlin.idea.core.moveCaret
import org.jetbrains.kotlin.idea.core.moveCaretIntoGeneratedElement
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.core.moveCaret
import org.jetbrains.kotlin.idea.core.moveCaretIntoGeneratedElement
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtPrimaryConstructor
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
@@ -55,7 +50,7 @@ class OverridesCompletion(
modifiers = emptySet()
}
fun complete(position: PsiElement) {
fun complete(position: PsiElement, declaration: KtCallableDeclaration?) {
val isConstructorParameter = position.getNonStrictParentOfType<KtPrimaryConstructor>() != null
val classOrObject = position.getNonStrictParentOfType<KtClassOrObject>() ?: return
@@ -63,9 +58,10 @@ class OverridesCompletion(
val members = OverrideMembersHandler(isConstructorParameter).collectMembersToGenerate(classOrObject)
for (memberObject in members) {
if (isConstructorParameter && memberObject.descriptor !is PropertyDescriptor) continue
val descriptor = memberObject.descriptor
if (declaration != null && !canOverride(descriptor, declaration)) continue
if (isConstructorParameter && descriptor !is PropertyDescriptor) continue
var lookupElement = lookupElementFactory.createLookupElement(descriptor)
var text = "override " + PRESENTATION_RENDERER.render(descriptor)
@@ -88,7 +84,7 @@ class OverridesCompletion(
val baseClassIcon = KotlinDescriptorIconProvider.getIcon(baseClass, baseClassDeclaration, 0)
lookupElement = object : LookupElementDecorator<LookupElement>(lookupElement) {
override fun getLookupString() = "override"
override fun getLookupString() = if (declaration == null) "override" else delegate.lookupString // don't use "override" as lookup string when already in the name of declaration
override fun getAllLookupStrings() = setOf(lookupString, delegate.lookupString)
override fun renderElement(presentation: LookupElementPresentation) {
@@ -102,8 +98,16 @@ class OverridesCompletion(
}
override fun handleInsert(context: InsertionContext) {
val chars = context.document.charsSequence
val dummyMemberText = if (isConstructorParameter) "override val dummy: Dummy ,@" else "override fun dummy() {}"
val dummyMemberHead = when {
declaration != null -> ""
isConstructorParameter -> "override val "
else -> "override fun "
}
val dummyMemberTail = when {
isConstructorParameter || declaration is KtProperty -> "dummy: Dummy ,@"
else -> "dummy() {}"
}
val dummyMemberText = dummyMemberHead + dummyMemberTail
context.document.replaceString(context.startOffset, context.tailOffset, dummyMemberText)
val psiDocumentManager = PsiDocumentManager.getInstance(context.project)
@@ -124,6 +128,7 @@ class OverridesCompletion(
psiDocumentManager.doPostponedOperationsAndUnblockDocument(context.document)
val offset = insertedMember.endOffset
val chars = context.document.charsSequence
val commaOffset = chars.indexOfSkippingSpace(',', offset)!!
val atCharOffset = chars.indexOfSkippingSpace('@', commaOffset + 1)!!
context.document.deleteString(offset, atCharOffset + 1)
@@ -141,4 +146,22 @@ class OverridesCompletion(
collector.addElement(lookupElement)
}
}
private fun canOverride(descriptorToOverride: CallableMemberDescriptor, declaration: KtCallableDeclaration): Boolean {
when (declaration) {
is KtFunction -> return descriptorToOverride is FunctionDescriptor
is KtValVarKeywordOwner -> {
if (descriptorToOverride !is PropertyDescriptor) return false
if (declaration.valOrVarKeyword?.node?.elementType == KtTokens.VAL_KEYWORD) {
return !descriptorToOverride.isVar
}
else {
return true // var can override either var or val
}
}
else -> return false
}
}
}
@@ -0,0 +1,27 @@
interface I {
fun foo()
val someVal: Int
var someVar: Int
}
class Base1 {
protected open fun bar(){}
}
open class Base2 : Base1() {
}
class A : Base2(), I {
fun x() {
unresolved1(unresolved2)
}
override fun <caret>
}
// EXIST: { itemText: "override fun bar() {...}", lookupString: "bar", tailText: null, typeText: "Base1", attributes: "" }
// EXIST: { itemText: "override fun foo() {...}", lookupString: "foo", tailText: null, typeText: "I", attributes: "bold" }
// EXIST: { itemText: "override operator fun equals(other: Any?): Boolean {...}", lookupString: "equals", tailText: null, typeText: "Any", attributes: "" }
// EXIST: { itemText: "override fun hashCode(): Int {...}", lookupString: "hashCode", tailText: null, typeText: "Any", attributes: "" }
// EXIST: { itemText: "override fun toString(): String {...}", lookupString: "toString", tailText: null, typeText: "Any", attributes: "" }
// NOTHING_ELSE
@@ -0,0 +1,23 @@
interface I {
fun foo()
val someVal: Int
var someVar: Int
}
class Base1 {
protected open fun bar(){}
}
open class Base2 : Base1() {
}
class A : Base2(), I {
fun x() {
unresolved1(unresolved2)
}
override val <caret>
}
// EXIST: { itemText: "override val someVal: Int", lookupString: "someVal", tailText: null, typeText: "I", attributes: "bold" }
// NOTHING_ELSE
@@ -0,0 +1,19 @@
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(override val <caret>) : Base2(), I
// EXIST: { itemText: "override val someVal: Int", tailText: null, typeText: "I", attributes: "bold" }
// EXIST: { itemText: "override val fromBase: String", tailText: null, typeText: "Base1", attributes: "" }
// NOTHING_ELSE
@@ -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 {
fun x() {
unresolved1(unresolved2)
}
override var <caret>
}
// EXIST: { itemText: "override val someVal: Int", lookupString: "someVal", tailText: null, typeText: "I", attributes: "bold" }
// EXIST: { itemText: "override var someVar: Int", lookupString: "someVar", tailText: null, typeText: "I", attributes: "bold" }
// NOTHING_ELSE
@@ -0,0 +1,10 @@
interface I {
fun ov_foo()
}
class A : I {
override fun ov<caret>
}
// ABSENT: override
// EXIST: { itemText: "override fun ov_foo() {...}", allLookupStrings: "ov_foo", tailText: null, typeText: "I", attributes: "bold" }
@@ -16,9 +16,9 @@ class A : Base2(), I {
}
// EXIST: { lookupString: "override", itemText: "override" }
// EXIST: { itemText: "override fun bar() {...}", lookupString: "override", tailText: null, typeText: "Base1", attributes: "" }
// EXIST: { itemText: "override operator fun equals(other: Any?): Boolean {...}", lookupString: "override", tailText: null, typeText: "Any", attributes: "" }
// EXIST: { itemText: "override fun foo() {...}", lookupString: "override", tailText: null, typeText: "I", attributes: "bold" }
// EXIST: { itemText: "override fun hashCode(): Int {...}", lookupString: "override", tailText: null, typeText: "Any", attributes: "" }
// EXIST: { itemText: "override val someVal: Int", lookupString: "override", tailText: null, typeText: "I", attributes: "bold" }
// EXIST: { itemText: "override var someVar: Int", lookupString: "override", tailText: null, typeText: "I", attributes: "bold" }
// EXIST: { itemText: "override fun bar() {...}", lookupString: "override", allLookupStrings: "bar, override", tailText: null, typeText: "Base1", attributes: "" }
// EXIST: { itemText: "override operator fun equals(other: Any?): Boolean {...}", lookupString: "override", allLookupStrings: "equals, override", tailText: null, typeText: "Any", attributes: "" }
// EXIST: { itemText: "override fun foo() {...}", lookupString: "override", allLookupStrings: "foo, override", tailText: null, typeText: "I", attributes: "bold" }
// EXIST: { itemText: "override fun hashCode(): Int {...}", lookupString: "override", allLookupStrings: "hashCode, override", tailText: null, typeText: "Any", attributes: "" }
// EXIST: { itemText: "override val someVal: Int", lookupString: "override", allLookupStrings: "override, someVal", tailText: null, typeText: "I", attributes: "bold" }
// EXIST: { itemText: "override var someVar: Int", lookupString: "override", allLookupStrings: "override, someVar", tailText: null, typeText: "I", attributes: "bold" }
@@ -0,0 +1,9 @@
interface I {
fun foo(p: Int)
}
class A : I {
override fun f<caret>
}
// ELEMENT_TEXT: "override fun foo(p: Int) {...}"
@@ -0,0 +1,11 @@
interface I {
fun foo(p: Int)
}
class A : I {
override fun foo(p: Int) {
<caret><selection>TODO("not implemented") //To change body of created functions use File | Settings | File Templates.</selection>
}
}
// ELEMENT_TEXT: "override fun foo(p: Int) {...}"
@@ -0,0 +1,6 @@
class A {
@Deprecated("") // it is deprecated
public override fun e<caret>
}
// ELEMENT_TEXT: "override operator fun equals(other: Any?): Boolean {...}"
@@ -0,0 +1,8 @@
class A {
@Deprecated("") // it is deprecated
public override fun equals(other: Any?): Boolean {
<caret><selection>return super.equals(other)</selection>
}
}
// ELEMENT_TEXT: "override operator fun equals(other: Any?): Boolean {...}"
@@ -0,0 +1,9 @@
interface I {
val someVal: String?
}
class A : I {
override val <caret>
}
// ELEMENT_TEXT: "override val someVal: String?"
@@ -0,0 +1,10 @@
interface I {
val someVal: String?
}
class A : I {
override val someVal: String?
get() = <caret><selection>throw UnsupportedOperationException()</selection>
}
// ELEMENT_TEXT: "override val someVal: String?"
@@ -0,0 +1,8 @@
interface I {
val someVal: java.io.File?
}
class A(override val s<caret>) : I {
}
// ELEMENT_TEXT: "override val someVal: File?"
@@ -0,0 +1,10 @@
import java.io.File
interface I {
val someVal: java.io.File?
}
class A(override val someVal: File?<caret>) : I {
}
// ELEMENT_TEXT: "override val someVal: File?"
@@ -4,7 +4,7 @@ interface I {
class A : I {
override fun foo(p: Int) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
<caret><selection>TODO("not implemented") //To change body of created functions use File | Settings | File Templates.</selection>
}
}
@@ -1913,6 +1913,30 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Override extends AbstractJSBasicCompletionTest {
@TestMetadata("AfterFunKeyword.kt")
public void testAfterFunKeyword() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/AfterFunKeyword.kt");
doTest(fileName);
}
@TestMetadata("AfterValKeyword.kt")
public void testAfterValKeyword() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/AfterValKeyword.kt");
doTest(fileName);
}
@TestMetadata("AfterValKeywordInConstructorParameter.kt")
public void testAfterValKeywordInConstructorParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/AfterValKeywordInConstructorParameter.kt");
doTest(fileName);
}
@TestMetadata("AfterVarKeyword.kt")
public void testAfterVarKeyword() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/AfterVarKeyword.kt");
doTest(fileName);
}
public void testAllFilesPresentInOverride() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/override"), Pattern.compile("^(.+)\\.kt$"), true);
}
@@ -1935,6 +1959,12 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
doTest(fileName);
}
@TestMetadata("NoOverrideAfterFunKeyword.kt")
public void testNoOverrideAfterFunKeyword() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/NoOverrideAfterFunKeyword.kt");
doTest(fileName);
}
@TestMetadata("PreferImplementToOverride.kt")
public void testPreferImplementToOverride() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/PreferImplementToOverride.kt");
@@ -1913,6 +1913,30 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Override extends AbstractJvmBasicCompletionTest {
@TestMetadata("AfterFunKeyword.kt")
public void testAfterFunKeyword() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/AfterFunKeyword.kt");
doTest(fileName);
}
@TestMetadata("AfterValKeyword.kt")
public void testAfterValKeyword() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/AfterValKeyword.kt");
doTest(fileName);
}
@TestMetadata("AfterValKeywordInConstructorParameter.kt")
public void testAfterValKeywordInConstructorParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/AfterValKeywordInConstructorParameter.kt");
doTest(fileName);
}
@TestMetadata("AfterVarKeyword.kt")
public void testAfterVarKeyword() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/AfterVarKeyword.kt");
doTest(fileName);
}
public void testAllFilesPresentInOverride() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/override"), Pattern.compile("^(.+)\\.kt$"), true);
}
@@ -1935,6 +1959,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
doTest(fileName);
}
@TestMetadata("NoOverrideAfterFunKeyword.kt")
public void testNoOverrideAfterFunKeyword() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/NoOverrideAfterFunKeyword.kt");
doTest(fileName);
}
@TestMetadata("PreferImplementToOverride.kt")
public void testPreferImplementToOverride() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/override/PreferImplementToOverride.kt");
@@ -426,6 +426,30 @@ public class BasicCompletionHandlerTestGenerated extends AbstractBasicCompletion
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Override extends AbstractBasicCompletionHandlerTest {
@TestMetadata("AfterFunKeyword.kt")
public void testAfterFunKeyword() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/override/AfterFunKeyword.kt");
doTest(fileName);
}
@TestMetadata("AfterFunKeywordKeepModifiersBefore.kt")
public void testAfterFunKeywordKeepModifiersBefore() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/override/AfterFunKeywordKeepModifiersBefore.kt");
doTest(fileName);
}
@TestMetadata("AfterValKeyword.kt")
public void testAfterValKeyword() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/override/AfterValKeyword.kt");
doTest(fileName);
}
@TestMetadata("AfterValKeywordInConstructorParameter.kt")
public void testAfterValKeywordInConstructorParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/override/AfterValKeywordInConstructorParameter.kt");
doTest(fileName);
}
public void testAllFilesPresentInOverride() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/handlers/basic/override"), Pattern.compile("^(.+)\\.kt$"), true);
}