Smart completion: anonymous object creation + rewrote some code using LookupElementDecorator

This commit is contained in:
Valentin Kipyatkov
2013-12-20 18:48:52 +04:00
parent 9627a335c2
commit 914a566094
16 changed files with 177 additions and 70 deletions
@@ -6,6 +6,17 @@
<item name='com.intellij.codeInsight.lookup.Lookup com.intellij.openapi.editor.Editor getEditor()'> <item name='com.intellij.codeInsight.lookup.Lookup com.intellij.openapi.editor.Editor getEditor()'>
<annotation name='org.jetbrains.annotations.NotNull'/> <annotation name='org.jetbrains.annotations.NotNull'/>
</item> </item>
<item name='com.intellij.codeInsight.lookup.LookupElement void handleInsert(com.intellij.codeInsight.completion.InsertionContext) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item
name='com.intellij.codeInsight.lookup.LookupElement void renderElement(com.intellij.codeInsight.lookup.LookupElementPresentation) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item
name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElement withAutoCompletionPolicy(com.intellij.codeInsight.lookup.AutoCompletionPolicy)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item <item
name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder create(com.intellij.psi.PsiNamedElement)'> name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder create(com.intellij.psi.PsiNamedElement)'>
<annotation name='org.jetbrains.annotations.NotNull'/> <annotation name='org.jetbrains.annotations.NotNull'/>
@@ -42,6 +53,10 @@
name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder withPresentableText(java.lang.String)'> name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder withPresentableText(java.lang.String)'>
<annotation name='org.jetbrains.annotations.NotNull'/> <annotation name='org.jetbrains.annotations.NotNull'/>
</item> </item>
<item
name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder withRenderer(com.intellij.codeInsight.lookup.LookupElementRenderer&lt;com.intellij.codeInsight.lookup.LookupElement&gt;)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item <item
name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder withStrikeoutness(boolean)'> name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder withStrikeoutness(boolean)'>
<annotation name='org.jetbrains.annotations.NotNull'/> <annotation name='org.jetbrains.annotations.NotNull'/>
@@ -50,6 +65,10 @@
name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder withTailText(java.lang.String)'> name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder withTailText(java.lang.String)'>
<annotation name='org.jetbrains.annotations.NotNull'/> <annotation name='org.jetbrains.annotations.NotNull'/>
</item> </item>
<item
name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder withTailText(java.lang.String, boolean)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item <item
name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder withTypeText(java.lang.String)'> name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder withTypeText(java.lang.String)'>
<annotation name='org.jetbrains.annotations.NotNull'/> <annotation name='org.jetbrains.annotations.NotNull'/>
@@ -58,6 +77,20 @@
name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder withTypeText(java.lang.String, boolean)'> name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder withTypeText(java.lang.String, boolean)'>
<annotation name='org.jetbrains.annotations.NotNull'/> <annotation name='org.jetbrains.annotations.NotNull'/>
</item> </item>
<item
name='com.intellij.codeInsight.lookup.LookupElementDecorator void renderElement(com.intellij.codeInsight.lookup.LookupElementPresentation) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item
name='com.intellij.codeInsight.lookup.LookupElementRenderer void renderElement(T, com.intellij.codeInsight.lookup.LookupElementPresentation)'>
<annotation name='jet.runtime.typeinfo.KotlinSignature'>
<val name="value" val="&quot;fun renderElement(element: T?, presentation: LookupElementPresentation): Unit&quot;"/>
</annotation>
</item>
<item
name='com.intellij.codeInsight.lookup.LookupElementRenderer void renderElement(T, com.intellij.codeInsight.lookup.LookupElementPresentation) 1'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item <item
name='com.intellij.codeInsight.lookup.MutableLookupElement com.intellij.codeInsight.lookup.MutableLookupElement&lt;T&gt; setInsertHandler(com.intellij.codeInsight.completion.InsertHandler&lt;? extends com.intellij.codeInsight.lookup.LookupElement&gt;)'> name='com.intellij.codeInsight.lookup.MutableLookupElement com.intellij.codeInsight.lookup.MutableLookupElement&lt;T&gt; setInsertHandler(com.intellij.codeInsight.completion.InsertHandler&lt;? extends com.intellij.codeInsight.lookup.LookupElement&gt;)'>
<annotation name='org.jetbrains.annotations.NotNull'/> <annotation name='org.jetbrains.annotations.NotNull'/>
@@ -26,6 +26,9 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiClass import com.intellij.psi.PsiClass
import com.intellij.psi.PsiModifier import com.intellij.psi.PsiModifier
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import com.intellij.openapi.editor.EditorModificationUtil
import org.jetbrains.jet.plugin.codeInsight.ImplementMethodsHandler
import com.intellij.psi.PsiDocumentManager
trait SmartCompletionData{ trait SmartCompletionData{
fun accepts(descriptor: DeclarationDescriptor): Boolean fun accepts(descriptor: DeclarationDescriptor): Boolean
@@ -122,12 +125,13 @@ private fun typeInstantiationItems(expectedType: JetType, resolveSession: Cancel
val typeConstructor: TypeConstructor = expectedType.getConstructor() val typeConstructor: TypeConstructor = expectedType.getConstructor()
val classifier: ClassifierDescriptor? = typeConstructor.getDeclarationDescriptor() val classifier: ClassifierDescriptor? = typeConstructor.getDeclarationDescriptor()
if (!(classifier is ClassDescriptor)) return listOf() if (!(classifier is ClassDescriptor)) return listOf()
if (classifier.getModality() == Modality.ABSTRACT) return listOf()
//TODO: check for constructor's visibility //TODO: check for constructor's visibility
val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, classifier) val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, classifier)
var lookupString = lookupElement.getLookupString()
val typeArgs = expectedType.getArguments() val typeArgs = expectedType.getArguments()
//TODO: shouldn't be method in DescriptorRenderer to render type arguments? //TODO: shouldn't be method in DescriptorRenderer to render type arguments?
val typeArgsText = val typeArgsText =
@@ -135,27 +139,59 @@ private fun typeInstantiationItems(expectedType: JetType, resolveSession: Cancel
"" ""
else else
typeArgs.map { DescriptorRenderer.TEXT.renderType(it.getType()) }.makeString(", ", "<", ">") typeArgs.map { DescriptorRenderer.TEXT.renderType(it.getType()) }.makeString(", ", "<", ">")
val presentableText = lookupElement.getLookupString() + typeArgsText + "()" var itemText = lookupElement.getLookupString() + typeArgsText
val constructors: Collection<ConstructorDescriptor> = classifier.getConstructors() val insertHandler: InsertHandler<LookupElement>
val caretPosition = var suppressAutoInsertion: Boolean = false
if (constructors.size == 0) if (classifier.getModality() == Modality.ABSTRACT) {
CaretPosition.AFTER_BRACKETS val constructorParenthesis = if (classifier.getKind() != ClassKind.TRAIT) "()" else ""
else if (constructors.size == 1) itemText += constructorParenthesis
if (constructors.first().getValueParameters().isEmpty()) CaretPosition.AFTER_BRACKETS else CaretPosition.IN_BRACKETS itemText = "object: " + itemText + "{...}"
else lookupString = "object" //?
CaretPosition.IN_BRACKETS insertHandler = object: InsertHandler<LookupElement>{
val insertHandler = JetFunctionInsertHandler(caretPosition, BracketType.PARENTHESIS) override fun handleInsert(context: InsertionContext, item: LookupElement) {
//TODO: insert import
//TODO: very bad code val editor = context.getEditor()
if (lookupElement is LookupElementBuilder) { EditorModificationUtil.insertStringAtCaret(editor, ": " + classifier.getName() + constructorParenthesis + " {}")
return listOf(lookupElement.withPresentableText(presentableText).withInsertHandler(insertHandler)) editor.getCaretModel().moveToOffset(editor.getCaretModel().getOffset() - 1)
PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments();
ImplementMethodsHandler().invoke(context.getProject(), editor, context.getFile(), true)
}
}
suppressAutoInsertion = true
} }
else if (lookupElement is JavaPsiClassReferenceElement) { else {
return listOf(lookupElement.setPresentableText(presentableText).setInsertHandler(insertHandler)) itemText += "()"
val constructors: Collection<ConstructorDescriptor> = classifier.getConstructors()
val caretPosition =
if (constructors.size == 0)
CaretPosition.AFTER_BRACKETS
else if (constructors.size == 1)
if (constructors.first().getValueParameters().isEmpty()) CaretPosition.AFTER_BRACKETS else CaretPosition.IN_BRACKETS
else
CaretPosition.IN_BRACKETS
insertHandler = JetFunctionInsertHandler(caretPosition, BracketType.PARENTHESIS)
} }
return listOf() val lookupElementDecorated = object: LookupElementDecorator<LookupElement>(lookupElement){
override fun getLookupString() = lookupString
override fun renderElement(presentation: LookupElementPresentation) {
lookupElement.renderElement(presentation)
presentation.setItemText(itemText)
}
override fun handleInsert(context: InsertionContext) {
insertHandler.handleInsert(context, lookupElement)
}
}
if (suppressAutoInsertion) {
return listOf(AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(lookupElementDecorated))
}
else{
return listOf(lookupElementDecorated)
}
} }
private fun thisItems(context: JetExpression, expectedType: JetType, bindingContext: BindingContext): Iterable<LookupElement> { private fun thisItems(context: JetExpression, expectedType: JetType, bindingContext: BindingContext): Iterable<LookupElement> {
@@ -273,27 +309,45 @@ private fun staticMembers(context: JetExpression, expectedType: JetType, resolve
.filterTo(descriptors) { it is ClassDescriptor && it.getKind() == ClassKind.ENUM_ENTRY } .filterTo(descriptors) { it is ClassDescriptor && it.getKind() == ClassKind.ENUM_ENTRY }
} }
return descriptors fun toLookupElement(descriptor: DeclarationDescriptor): LookupElement {
.filter { !(it is DeclarationDescriptorWithVisibility) || Visibilities.isVisible(it, scope.getContainingDeclaration()) } val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor)
.map { val lookupString = classDescriptor.getName().asString() + "." + lookupElement.getLookupString()
val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, it) var itemText = lookupString
val presentation = LookupElementPresentation() val insertHandler: InsertHandler<LookupElement>?
lookupElement.renderElement(presentation) if (descriptor is FunctionDescriptor) {
var builder = LookupElementBuilder.create(lookupElement.getObject(), classDescriptor.getName().asString() + "." + lookupElement.getLookupString()) itemText += "()"
.withIcon(presentation.getIcon()) val caretPosition = if (descriptor.getValueParameters().empty) CaretPosition.AFTER_BRACKETS else CaretPosition.IN_BRACKETS
.withStrikeoutness(presentation.isStrikeout()) insertHandler = JetFunctionInsertHandler(caretPosition, BracketType.PARENTHESIS)
.withTailText(" (" + DescriptorUtils.getFqName(classDescriptor.getContainingDeclaration()) + ")") }
.withTypeText(if (!presentation.getTypeText().isNullOrEmpty()) else {
presentation.getTypeText() insertHandler = null
else }
DescriptorRenderer.TEXT.renderType(classDescriptor.getDefaultType())) return object: LookupElementDecorator<LookupElement>(lookupElement){
if (it is FunctionDescriptor) { override fun getLookupString() = lookupString
builder = builder.withPresentableText(builder.getLookupString() + "()")
val caretPosition = if (it.getValueParameters().empty) CaretPosition.AFTER_BRACKETS else CaretPosition.IN_BRACKETS override fun renderElement(presentation: LookupElementPresentation) {
builder = builder.withInsertHandler(JetFunctionInsertHandler(caretPosition, BracketType.PARENTHESIS)) lookupElement.renderElement(presentation)
} presentation.setItemText(itemText)
builder presentation.setTailText(" (" + DescriptorUtils.getFqName(classDescriptor.getContainingDeclaration()) + ")")
if (presentation.getTypeText().isNullOrEmpty()) {
presentation.setTypeText(DescriptorRenderer.TEXT.renderType(classDescriptor.getDefaultType()))
} }
}
override fun handleInsert(context: InsertionContext) {
if (insertHandler != null) {
insertHandler.handleInsert(context, lookupElement)
}
else{
lookupElement.handleInsert(context)
}
}
}
}
return descriptors
.filter { !(it is DeclarationDescriptorWithVisibility) || Visibilities.isVisible(it, scope.getContainingDeclaration()) }
.map({toLookupElement(it)}) //TODO: use ::toLookupElement but it causes compiler to crash
} }
private fun isSubtypeOf(t: JetType, expectedType: JetType): Boolean{ private fun isSubtypeOf(t: JetType, expectedType: JetType): Boolean{
@@ -0,0 +1,3 @@
trait Foo
var a : Foo = <caret>
@@ -0,0 +1,3 @@
trait Foo
var a : Foo = object: Foo {<caret>}
@@ -0,0 +1,3 @@
abstract class Foo
var a : Foo = <caret>
@@ -0,0 +1,3 @@
abstract class Foo
var a : Foo = object: Foo() {<caret>}
@@ -0,0 +1 @@
var r : Runnable = <caret>
@@ -0,0 +1,5 @@
var r : Runnable = object: Runnable {
override fun run() {
throw UnsupportedOperationException()
}
}
@@ -0,0 +1,6 @@
trait Foo
var a : Foo = <caret>
// ABSENT: Foo
// EXIST: { lookupString:"object", itemText:"object: Foo{...}" }
@@ -0,0 +1,6 @@
abstract class Foo
var a : Foo = <caret>
// ABSENT: Foo
// EXIST: { lookupString:"object", itemText:"object: Foo(){...}" }
@@ -0,0 +1,4 @@
var a : Appendable = <caret>
// ABSENT: Appendable
// EXIST: { lookupString:"object", itemText:"object: Appendable{...}" }
@@ -1,7 +0,0 @@
abstract class Foo
fun foo(p : Any){
var a : Foo = <caret>
}
// ABSENT: Foo
@@ -1,3 +0,0 @@
var a : Appendable = <caret>
// ABSENT: Appendable
@@ -1,7 +0,0 @@
trait Foo
fun foo(p : Any){
var a : Foo = <caret>
}
// ABSENT: Foo
@@ -36,6 +36,21 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/completion/smart"), Pattern.compile("^(.+)\\.kt$"), true); JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/completion/smart"), Pattern.compile("^(.+)\\.kt$"), true);
} }
@TestMetadata("AnonymousObject1.kt")
public void testAnonymousObject1() throws Exception {
doTest("idea/testData/completion/smart/AnonymousObject1.kt");
}
@TestMetadata("AnonymousObject2.kt")
public void testAnonymousObject2() throws Exception {
doTest("idea/testData/completion/smart/AnonymousObject2.kt");
}
@TestMetadata("AnonymousObjectForJavaInterface.kt")
public void testAnonymousObjectForJavaInterface() throws Exception {
doTest("idea/testData/completion/smart/AnonymousObjectForJavaInterface.kt");
}
@TestMetadata("AutoCastedType.kt") @TestMetadata("AutoCastedType.kt")
public void testAutoCastedType() throws Exception { public void testAutoCastedType() throws Exception {
doTest("idea/testData/completion/smart/AutoCastedType.kt"); doTest("idea/testData/completion/smart/AutoCastedType.kt");
@@ -151,21 +166,6 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
doTest("idea/testData/completion/smart/MethodCallArgument.kt"); doTest("idea/testData/completion/smart/MethodCallArgument.kt");
} }
@TestMetadata("NoConstructorForAbstract.kt")
public void testNoConstructorForAbstract() throws Exception {
doTest("idea/testData/completion/smart/NoConstructorForAbstract.kt");
}
@TestMetadata("NoConstructorForJavaInterface.kt")
public void testNoConstructorForJavaInterface() throws Exception {
doTest("idea/testData/completion/smart/NoConstructorForJavaInterface.kt");
}
@TestMetadata("NoConstructorForTrait.kt")
public void testNoConstructorForTrait() throws Exception {
doTest("idea/testData/completion/smart/NoConstructorForTrait.kt");
}
@TestMetadata("NoConstructorWithQualifier.kt") @TestMetadata("NoConstructorWithQualifier.kt")
public void testNoConstructorWithQualifier() throws Exception { public void testNoConstructorWithQualifier() throws Exception {
doTest("idea/testData/completion/smart/NoConstructorWithQualifier.kt"); doTest("idea/testData/completion/smart/NoConstructorWithQualifier.kt");
@@ -51,4 +51,7 @@ public class SmartCompletionHandlerTest() : CompletionHandlerTestBase() {
fun testTabReplaceExpression2() = doTest(1, "sss", null, '\t') fun testTabReplaceExpression2() = doTest(1, "sss", null, '\t')
fun testTabReplaceExpression3() = doTest(1, "sss", null, '\t') fun testTabReplaceExpression3() = doTest(1, "sss", null, '\t')
fun testTabReplaceOperand() = doTest(1, "b3", null, '\t') fun testTabReplaceOperand() = doTest(1, "b3", null, '\t')
fun testAnonymousObject1() = doTest(1, "object", null, '\t')
fun testAnonymousObject2() = doTest(1, "object", null, '\t')
fun testAnonymousObject3() = doTest(1, "object", null, '\t')
} }