Smart completion: imports insertion + fixed both behaviour and presentation of qualified members in lookup
This commit is contained in:
@@ -6,6 +6,9 @@
|
||||
<item name='com.intellij.codeInsight.lookup.Lookup com.intellij.openapi.editor.Editor getEditor()'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='com.intellij.codeInsight.lookup.Lookup java.util.List<com.intellij.codeInsight.lookup.LookupElement> getItems()'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='com.intellij.codeInsight.lookup.LookupElement void handleInsert(com.intellij.codeInsight.completion.InsertionContext) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
@@ -85,6 +85,7 @@ public final class FqNameUnsafe extends FqNameBase {
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String asString() {
|
||||
return fqName;
|
||||
@@ -267,6 +268,7 @@ public final class FqNameUnsafe extends FqNameBase {
|
||||
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toString() {
|
||||
return isRoot() ? ROOT_NAME.asString() : fqName;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
|
||||
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
|
||||
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
|
||||
import org.jetbrains.jet.lang.types.JetType;
|
||||
import org.jetbrains.jet.lang.types.TypeProjection;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface DescriptorRenderer extends Renderer<DeclarationDescriptor> {
|
||||
DescriptorRenderer COMPACT_WITH_MODIFIERS = new DescriptorRendererBuilder().setWithDefinedIn(false).build();
|
||||
@@ -61,6 +64,9 @@ public interface DescriptorRenderer extends Renderer<DeclarationDescriptor> {
|
||||
@NotNull
|
||||
String renderType(@NotNull JetType type);
|
||||
|
||||
@NotNull
|
||||
String renderTypeArguments(@NotNull List<TypeProjection> typeArguments);
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
String render(@NotNull DeclarationDescriptor declarationDescriptor);
|
||||
|
||||
@@ -201,6 +201,17 @@ public class DescriptorRendererImpl implements DescriptorRenderer {
|
||||
return escape(renderTypeWithoutEscape(type));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String renderTypeArguments(@NotNull List<TypeProjection> typeArguments) {
|
||||
if (typeArguments.isEmpty()) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<");
|
||||
appendTypeProjections(typeArguments, sb);
|
||||
sb.append(">");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String renderTypeWithoutEscape(@NotNull JetType type) {
|
||||
if (type == CANT_INFER_LAMBDA_PARAM_TYPE || type == CANT_INFER_TYPE_PARAMETER) {
|
||||
return "???";
|
||||
|
||||
@@ -29,6 +29,9 @@ import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.openapi.editor.EditorModificationUtil
|
||||
import org.jetbrains.jet.plugin.codeInsight.ImplementMethodsHandler
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
|
||||
import org.jetbrains.jet.plugin.codeInsight.ShortenReferences
|
||||
|
||||
trait SmartCompletionData{
|
||||
fun accepts(descriptor: DeclarationDescriptor): Boolean
|
||||
@@ -133,30 +136,31 @@ private fun typeInstantiationItems(expectedType: JetType, resolveSession: Cancel
|
||||
var lookupString = lookupElement.getLookupString()
|
||||
|
||||
val typeArgs = expectedType.getArguments()
|
||||
//TODO: shouldn't be method in DescriptorRenderer to render type arguments?
|
||||
val typeArgsText =
|
||||
if (typeArgs.isEmpty())
|
||||
""
|
||||
else
|
||||
typeArgs.map { DescriptorRenderer.TEXT.renderType(it.getType()) }.makeString(", ", "<", ">")
|
||||
var itemText = lookupElement.getLookupString() + typeArgsText
|
||||
var itemText = lookupString + DescriptorRenderer.TEXT.renderTypeArguments(typeArgs)
|
||||
|
||||
val insertHandler: InsertHandler<LookupElement>
|
||||
var suppressAutoInsertion: Boolean = false
|
||||
val typeText = DescriptorUtils.getFqName(classifier).toString() + DescriptorRenderer.SOURCE_CODE.renderTypeArguments(typeArgs)
|
||||
if (classifier.getModality() == Modality.ABSTRACT) {
|
||||
val constructorParenthesis = if (classifier.getKind() != ClassKind.TRAIT) "()" else ""
|
||||
itemText += constructorParenthesis
|
||||
itemText = "object: " + itemText + "{...}"
|
||||
lookupString = "object" //?
|
||||
insertHandler = object: InsertHandler<LookupElement>{
|
||||
override fun handleInsert(context: InsertionContext, item: LookupElement) {
|
||||
//TODO: insert import
|
||||
val editor = context.getEditor()
|
||||
EditorModificationUtil.insertStringAtCaret(editor, ": " + classifier.getName() + constructorParenthesis + " {}")
|
||||
editor.getCaretModel().moveToOffset(editor.getCaretModel().getOffset() - 1)
|
||||
PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments();
|
||||
ImplementMethodsHandler().invoke(context.getProject(), editor, context.getFile(), true)
|
||||
insertHandler = InsertHandler<LookupElement> { (context, item) ->
|
||||
val editor = context.getEditor()
|
||||
val startOffset = context.getStartOffset()
|
||||
val text = "object: $typeText$constructorParenthesis {}"
|
||||
editor.getDocument().replaceString(startOffset, context.getTailOffset(), text)
|
||||
editor.getCaretModel().moveToOffset(startOffset + text.length - 1)
|
||||
|
||||
PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments();
|
||||
val file = context.getFile() as JetFile
|
||||
val element = PsiTreeUtil.findElementOfClassAtRange(file, startOffset, startOffset + text.length, javaClass<JetElement>())
|
||||
if (element != null) {
|
||||
ShortenReferences.process(element)
|
||||
}
|
||||
|
||||
ImplementMethodsHandler().invoke(context.getProject(), editor, context.getFile(), true)
|
||||
}
|
||||
suppressAutoInsertion = true
|
||||
}
|
||||
@@ -170,7 +174,23 @@ private fun typeInstantiationItems(expectedType: JetType, resolveSession: Cancel
|
||||
if (constructors.first().getValueParameters().isEmpty()) CaretPosition.AFTER_BRACKETS else CaretPosition.IN_BRACKETS
|
||||
else
|
||||
CaretPosition.IN_BRACKETS
|
||||
insertHandler = JetFunctionInsertHandler(caretPosition, BracketType.PARENTHESIS)
|
||||
insertHandler = InsertHandler<LookupElement> { (context, item) ->
|
||||
val editor = context.getEditor()
|
||||
val startOffset = context.getStartOffset()
|
||||
val text = typeText + "()"
|
||||
editor.getDocument().replaceString(startOffset, context.getTailOffset(), text)
|
||||
val endOffset = startOffset + text.length
|
||||
editor.getCaretModel().moveToOffset(if (caretPosition == CaretPosition.IN_BRACKETS) endOffset - 1 else endOffset)
|
||||
|
||||
//TODO: autopopup parameter info and other functionality from JetFunctionInsertHandler
|
||||
|
||||
PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments();
|
||||
val file = context.getFile() as JetFile
|
||||
val element = PsiTreeUtil.findElementOfClassAtRange(file, startOffset, endOffset, javaClass<JetElement>())
|
||||
if (element != null) {
|
||||
ShortenReferences.process(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val lookupElementDecorated = object: LookupElementDecorator<LookupElement>(lookupElement){
|
||||
@@ -311,36 +331,62 @@ private fun staticMembers(context: JetExpression, expectedType: JetType, resolve
|
||||
|
||||
fun toLookupElement(descriptor: DeclarationDescriptor): LookupElement {
|
||||
val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor)
|
||||
val lookupString = classDescriptor.getName().asString() + "." + lookupElement.getLookupString()
|
||||
var itemText = lookupString
|
||||
val insertHandler: InsertHandler<LookupElement>?
|
||||
val qualifierPresentation = classDescriptor.getName().asString()
|
||||
val lookupString = qualifierPresentation + "." + lookupElement.getLookupString()
|
||||
val qualifierText = DescriptorUtils.getFqName(classDescriptor).asString() //TODO: escape keywords
|
||||
|
||||
val caretPosition: CaretPosition?
|
||||
if (descriptor is FunctionDescriptor) {
|
||||
itemText += "()"
|
||||
val caretPosition = if (descriptor.getValueParameters().empty) CaretPosition.AFTER_BRACKETS else CaretPosition.IN_BRACKETS
|
||||
insertHandler = JetFunctionInsertHandler(caretPosition, BracketType.PARENTHESIS)
|
||||
caretPosition = if (descriptor.getValueParameters().empty) CaretPosition.AFTER_BRACKETS else CaretPosition.IN_BRACKETS
|
||||
}
|
||||
else {
|
||||
insertHandler = null
|
||||
caretPosition = null
|
||||
}
|
||||
|
||||
val insertHandler = InsertHandler<LookupElement> { (context, item) ->
|
||||
val editor = context.getEditor()
|
||||
val startOffset = context.getStartOffset()
|
||||
var text = qualifierText + "." + descriptor.getName().asString() //TODO: escape
|
||||
if (descriptor is FunctionDescriptor) {
|
||||
text += "()"
|
||||
//TODO: autopopup parameter info and other functionality from JetFunctionInsertHandler
|
||||
}
|
||||
|
||||
editor.getDocument().replaceString(startOffset, context.getTailOffset(), text)
|
||||
val endOffset = startOffset + text.length
|
||||
editor.getCaretModel().moveToOffset(if (caretPosition == CaretPosition.IN_BRACKETS) endOffset - 1 else endOffset)
|
||||
|
||||
PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments();
|
||||
val file = context.getFile() as JetFile
|
||||
val element = PsiTreeUtil.findElementOfClassAtRange(file, startOffset, startOffset + qualifierText.length, javaClass<JetElement>())
|
||||
if (element != null) {
|
||||
ShortenReferences.process(element)
|
||||
}
|
||||
}
|
||||
|
||||
return object: LookupElementDecorator<LookupElement>(lookupElement){
|
||||
override fun getLookupString() = lookupString
|
||||
|
||||
override fun renderElement(presentation: LookupElementPresentation) {
|
||||
lookupElement.renderElement(presentation)
|
||||
presentation.setItemText(itemText)
|
||||
presentation.setTailText(" (" + DescriptorUtils.getFqName(classDescriptor.getContainingDeclaration()) + ")")
|
||||
|
||||
presentation.setItemText(qualifierPresentation + "." + presentation.getItemText())
|
||||
|
||||
val tailText = " (" + DescriptorUtils.getFqName(classDescriptor.getContainingDeclaration()) + ")"
|
||||
if (descriptor is FunctionDescriptor) {
|
||||
presentation.appendTailText(tailText, true)
|
||||
}
|
||||
else{
|
||||
presentation.setTailText(tailText, true)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
insertHandler.handleInsert(context, lookupElement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
val c: java.io.Closeable = <caret>
|
||||
@@ -0,0 +1,7 @@
|
||||
import java.io.Closeable
|
||||
|
||||
val c: java.io.Closeable = object: Closeable {
|
||||
override fun close() {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import java.util.HashMap
|
||||
import java.util.List
|
||||
|
||||
fun foo(p: HashMap<String, List<Int>>){}
|
||||
|
||||
fun f(){
|
||||
foo(<caret>)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import java.util.HashMap
|
||||
import java.util.List
|
||||
|
||||
fun foo(p: HashMap<String, List<Int>>){}
|
||||
|
||||
fun f(){
|
||||
foo(HashMap<String, List<Int>>())
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
fun foo(p: java.util.BitSet){}
|
||||
class X<T> {
|
||||
fun foo(p: java.util.HashMap<java.io.File, T>){}
|
||||
|
||||
fun f(){
|
||||
foo(<caret>)
|
||||
fun f(){
|
||||
foo(<caret>)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import java.util.BitSet
|
||||
import java.util.HashMap
|
||||
import java.io.File
|
||||
|
||||
fun foo(p: java.util.BitSet){}
|
||||
class X<T> {
|
||||
fun foo(p: java.util.HashMap<java.io.File, T>){}
|
||||
|
||||
fun f(){
|
||||
foo(BitSet(<caret>))
|
||||
fun f(){
|
||||
foo(HashMap<File, T>(<caret>))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
class X<T> {
|
||||
fun foo(p: java.util.HashMap<T, java.util.AbstractMap<T, java.io.File>>){}
|
||||
|
||||
fun f(){
|
||||
foo(<caret>)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import java.util.HashMap
|
||||
import java.util.AbstractMap
|
||||
import java.io.File
|
||||
|
||||
class X<T> {
|
||||
fun foo(p: java.util.HashMap<T, java.util.AbstractMap<T, java.io.File>>){}
|
||||
|
||||
fun f(){
|
||||
foo(HashMap<T, AbstractMap<T, File>>(<caret>))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
enum class Foo {
|
||||
X
|
||||
Y
|
||||
}
|
||||
|
||||
fun foo(){
|
||||
val f : Foo = <caret>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
enum class Foo {
|
||||
X
|
||||
Y
|
||||
}
|
||||
|
||||
fun foo(){
|
||||
val f : Foo = Foo.X<caret>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo(){
|
||||
val e : java.lang.annotation.ElementType = <caret>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import java.lang.annotation.ElementType
|
||||
|
||||
fun foo(){
|
||||
val e : java.lang.annotation.ElementType = ElementType.FIELD<caret>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import java.util.Locale
|
||||
|
||||
fun foo(){
|
||||
var l : Locale = <caret>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import java.util.Locale
|
||||
|
||||
fun foo(){
|
||||
var l : Locale = Locale.ENGLISH<caret>
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import java.util.Locale
|
||||
|
||||
fun foo(){
|
||||
var l : java.util.Locale = Locale.ENGLISH
|
||||
var l : java.util.Locale = Locale.ENGLISH<caret>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo(){
|
||||
val l : java.util.Calendar = <caret>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import java.util.Calendar
|
||||
|
||||
fun foo(){
|
||||
val l : java.util.Calendar = Calendar.getInstance(<caret>)
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
fun foo(){
|
||||
val l : java.lang.Thread = <caret>
|
||||
val l : java.util.Calendar = <caret>
|
||||
}
|
||||
|
||||
// EXIST: { lookupString:"Thread.currentThread", itemText:"Thread.currentThread()", tailText:" (java.lang)", typeText:"Thread" }
|
||||
// EXIST: { lookupString:"Calendar.getInstance", itemText:"Calendar.getInstance", tailText:"() (java.util)", typeText:"Calendar" }
|
||||
// EXIST: { lookupString:"Calendar.getInstance", itemText:"Calendar.getInstance", tailText:"(TimeZone) (java.util)", typeText:"Calendar" }
|
||||
// EXIST: { lookupString:"Calendar.getInstance", itemText:"Calendar.getInstance", tailText:"(TimeZone, Locale) (java.util)", typeText:"Calendar" }
|
||||
|
||||
@@ -68,31 +68,31 @@ public abstract class CompletionHandlerTestBase() : JetLightCodeInsightFixtureTe
|
||||
|
||||
private fun getExistentLookupElement(lookupString : String?, tailText : String?) : LookupElement? {
|
||||
val lookup = LookupManager.getInstance(getProject())?.getActiveLookup() as LookupImpl?
|
||||
if (lookup == null) return null
|
||||
|
||||
var foundElement : LookupElement? = null
|
||||
if (lookup != null) {
|
||||
val presentation = LookupElementPresentation()
|
||||
for (lookupElement in lookup.getItems()!!) {
|
||||
|
||||
val lookupOk : Boolean
|
||||
if (lookupString != null) {
|
||||
lookupOk = lookupElement.getLookupString().contains(lookupString)
|
||||
}
|
||||
else {
|
||||
lookupOk = true
|
||||
}
|
||||
val presentation = LookupElementPresentation()
|
||||
for (lookupElement in lookup.getItems()) {
|
||||
val lookupOk : Boolean
|
||||
if (lookupString != null) {
|
||||
lookupOk = lookupElement.getLookupString().contains(lookupString)
|
||||
}
|
||||
else {
|
||||
lookupOk = true
|
||||
}
|
||||
|
||||
if (lookupOk) {
|
||||
val tailOk : Boolean
|
||||
if (tailText != null) {
|
||||
lookupElement.renderElement(presentation)
|
||||
val itemTailText : String? = presentation.getTailText()
|
||||
tailOk = itemTailText != null && (itemTailText.contains(tailText))
|
||||
tailOk = itemTailText != null && itemTailText.contains(tailText)
|
||||
}
|
||||
else {
|
||||
tailOk = true
|
||||
}
|
||||
|
||||
if (lookupOk && tailOk) {
|
||||
if (tailOk) {
|
||||
if (foundElement != null) {
|
||||
Assert.fail("Several elements satisfy to completion restrictions")
|
||||
}
|
||||
@@ -102,6 +102,7 @@ public abstract class CompletionHandlerTestBase() : JetLightCodeInsightFixtureTe
|
||||
}
|
||||
}
|
||||
|
||||
if (foundElement == null) error("No element satisfy completion restrictions")
|
||||
return foundElement
|
||||
}
|
||||
|
||||
|
||||
@@ -41,11 +41,17 @@ public class SmartCompletionHandlerTest() : CompletionHandlerTestBase() {
|
||||
fun testConstructorWithParameters() = doTest()
|
||||
fun testConstructorForNullable() = doTest()
|
||||
fun testConstructorForJavaClass() = doTest()
|
||||
//fun testConstructorInsertsImport() = doTest() //TODO
|
||||
fun testConstructorForGenericType() = doTest()
|
||||
fun testConstructorInsertsImport() = doTest()
|
||||
fun testConstructorInsertsImport2() = doTest()
|
||||
fun testJavaStaticMethod() = doTest(1, "Thread.currentThread", null, '\n')
|
||||
fun testJavaStaticMethodInsertsImport() = doTest(1, "Calendar.getInstance", "(TimeZone)", '\n')
|
||||
fun testClassObjectMethod1() = doTest(1, "K.bar", null, '\n')
|
||||
fun testClassObjectMethod2() = doTest(1, "K.bar", null, '\n')
|
||||
//fun testJavaStaticFieldInsertImport() = doTest(1, "Locale.ENGLISH", null, '\n') //TODO
|
||||
fun testEnumMember() = doTest(1, "Foo.X", null, '\n')
|
||||
fun testJavaEnumMemberInsertsImport() = doTest(1, "ElementType.FIELD", null, '\n')
|
||||
fun testJavaStaticField() = doTest(1, "Locale.ENGLISH", null, '\n')
|
||||
fun testJavaStaticFieldInsertImport() = doTest(1, "Locale.ENGLISH", null, '\n')
|
||||
fun testTabReplaceIdentifier() = doTest(1, "ss", null, '\t')
|
||||
fun testTabReplaceExpression() = doTest(1, "sss", null, '\t')
|
||||
fun testTabReplaceExpression2() = doTest(1, "sss", null, '\t')
|
||||
@@ -54,4 +60,5 @@ public class SmartCompletionHandlerTest() : CompletionHandlerTestBase() {
|
||||
fun testAnonymousObject1() = doTest(1, "object", null, '\t')
|
||||
fun testAnonymousObject2() = doTest(1, "object", null, '\t')
|
||||
fun testAnonymousObject3() = doTest(1, "object", null, '\t')
|
||||
fun testAnonymousObjectInsertsImport() = doTest(1, "object", null, '\t')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user