Completion of parameter name/types from the current file

This commit is contained in:
Valentin Kipyatkov
2015-06-18 17:51:47 +03:00
parent 22ad1389f5
commit 17500cef98
15 changed files with 233 additions and 69 deletions
@@ -125,7 +125,9 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess
protected val prefixMatcher: PrefixMatcher = this.resultSet.getPrefixMatcher()
protected val referenceVariantsHelper: ReferenceVariantsHelper = ReferenceVariantsHelper(bindingContext, moduleDescriptor, project, { isVisibleDescriptor(it) })
protected val isVisibleFilter: (DeclarationDescriptor) -> Boolean = { isVisibleDescriptor(it) }
protected val referenceVariantsHelper: ReferenceVariantsHelper = ReferenceVariantsHelper(bindingContext, moduleDescriptor, project, isVisibleFilter)
protected val receiversData: ReferenceVariantsHelper.ReceiversData? = reference?.let { referenceVariantsHelper.getReferenceVariantsReceivers(it.expression) }
@@ -165,7 +167,7 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess
}
protected val indicesHelper: KotlinIndicesHelper
get() = KotlinIndicesHelper(project, resolutionFacade, searchScope, moduleDescriptor, { isVisibleDescriptor(it) })
get() = KotlinIndicesHelper(project, resolutionFacade, searchScope, moduleDescriptor, isVisibleFilter)
protected fun isVisibleDescriptor(descriptor: DeclarationDescriptor): Boolean {
if (descriptor is DeclarationDescriptorWithVisibility && inDescriptor != null) {
@@ -356,9 +358,13 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration,
}
if (completionKind != CompletionKind.NAMED_ARGUMENTS_ONLY) {
collector.addDescriptorElements(referenceVariants, suppressAutoInsertion = false)
parameterNameAndTypeCompletion?.addFromParametersInFile(position, resolutionFacade, isVisibleFilter)
flushToResultSet()
parameterNameAndTypeCompletion?.addFromImports(position, bindingContext, { isVisibleDescriptor(it) })
parameterNameAndTypeCompletion?.addFromImportedClasses(position, bindingContext, isVisibleFilter)
flushToResultSet()
collector.addDescriptorElements(referenceVariants, suppressAutoInsertion = false)
val keywordsPrefix = prefix.substringBefore('@') // if there is '@' in the prefix - use shorter prefix to not loose 'this' etc
KeywordCompletion.complete(expression ?: parameters.getPosition(), keywordsPrefix) { lookupElement ->
@@ -434,7 +440,7 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration,
}
}
parameterNameAndTypeCompletion?.addAll(parameters, indicesHelper)
parameterNameAndTypeCompletion?.addFromAllClasses(parameters, indicesHelper)
}
}
@@ -456,7 +462,7 @@ class SmartCompletionSession(configuration: CompletionSessionConfiguration, para
addFunctionLiteralArgumentCompletions()
val completion = SmartCompletion(expression, resolutionFacade, moduleDescriptor,
bindingContext, { isVisibleDescriptor(it) }, inDescriptor, prefixMatcher, originalSearchScope,
bindingContext, isVisibleFilter, inDescriptor, prefixMatcher, originalSearchScope,
mapper, lookupElementFactory)
val result = completion.execute()
if (result != null) {
@@ -22,6 +22,7 @@ import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.util.Key
import com.intellij.patterns.ElementPattern
import com.intellij.patterns.StandardPatterns
import com.intellij.psi.PsiDocumentManager
import com.intellij.util.PlatformIcons
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
@@ -29,9 +30,9 @@ import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.completion.handlers.CastReceiverInsertHandler
import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler
import org.jetbrains.kotlin.idea.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.renderer.render
@@ -339,6 +340,62 @@ fun LookupElementFactory.createBackingFieldLookupElement(
}.assignPriority(ItemPriority.BACKING_FIELD)
}
fun LookupElementFactory.createLookupElementForType(type: JetType): LookupElement? {
if (type.isError()) return null
val classifier = type.getConstructor().getDeclarationDescriptor() ?: return null
val lookupElement = createLookupElement(classifier, false)
var itemText = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(type)
val typeText = IdeDescriptorRenderers.SOURCE_CODE.renderType(type)
var packageName: FqName? = null
if (!KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(type)) {
var container = classifier.getContainingDeclaration()
while (container is ClassDescriptor) {
container = container.getContainingDeclaration()
}
if (container is PackageFragmentDescriptor) {
packageName = container.fqName
}
}
val insertHandler: InsertHandler<LookupElement> = object : InsertHandler<LookupElement> {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
context.getDocument().replaceString(context.getStartOffset(), context.getTailOffset(), typeText)
context.setTailOffset(context.getStartOffset() + typeText.length())
shortenReferences(context, context.getStartOffset(), context.getTailOffset())
}
}
class TypeLookupElement : LookupElementDecorator<LookupElement>(lookupElement) {
val typeText = typeText
override fun equals(other: Any?) = other is TypeLookupElement && typeText == other.typeText
override fun hashCode() = typeText.hashCode()
override fun renderElement(presentation: LookupElementPresentation) {
getDelegate().renderElement(presentation)
presentation.setItemText(itemText)
presentation.clearTail()
if (packageName != null) {
presentation.appendTailText(" ($packageName)", true)
}
}
override fun handleInsert(context: InsertionContext) {
insertHandler.handleInsert(context, getDelegate())
}
}
return TypeLookupElement()
}
fun shortenReferences(context: InsertionContext, startOffset: Int, endOffset: Int) {
PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments();
ShortenReferences.DEFAULT.process(context.getFile() as JetFile, startOffset, endOffset)
}
fun <T> ElementPattern<T>.and(rhs: ElementPattern<T>) = StandardPatterns.and(this, rhs)
fun <T> ElementPattern<T>.andNot(rhs: ElementPattern<T>) = StandardPatterns.and(this, StandardPatterns.not(rhs))
fun <T> ElementPattern<T>.or(rhs: ElementPattern<T>) = StandardPatterns.or(this, rhs)
@@ -23,8 +23,10 @@ import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.codeInsight.lookup.LookupElementPresentation
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.codeStyle.CodeStyleSettingsManager
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
@@ -33,14 +35,15 @@ import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper
import org.jetbrains.kotlin.idea.core.formatter.JetCodeStyleSettings
import org.jetbrains.kotlin.idea.core.refactoring.EmptyValidator
import org.jetbrains.kotlin.idea.core.refactoring.JetNameSuggester
import org.jetbrains.kotlin.psi.JetClassOrObject
import org.jetbrains.kotlin.psi.JetExpression
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.idea.util.approximateFlexibleTypes
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.JetScope
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.kotlin.types.JetType
class ParameterNameAndTypeCompletion(
private val collector: LookupElementsCollector,
@@ -50,10 +53,10 @@ class ParameterNameAndTypeCompletion(
) {
private val modifiedPrefixMatcher = prefixMatcher.cloneWithPrefix(prefixMatcher.getPrefix().capitalize())
public fun addFromImports(context: PsiElement, bindingContext: BindingContext, visibilityFilter: (DeclarationDescriptor) -> Boolean) {
public fun addFromImportedClasses(position: PsiElement, bindingContext: BindingContext, visibilityFilter: (DeclarationDescriptor) -> Boolean) {
if (prefixMatcher.getPrefix().isEmpty()) return
val resolutionScope = context.getResolutionScope(bindingContext)
val resolutionScope = position.getResolutionScope(bindingContext)
val classifiers = resolutionScope.getDescriptorsFiltered(DescriptorKindFilter.NON_SINGLETON_CLASSIFIERS, modifiedPrefixMatcher.asNameFilter())
for (classifier in classifiers) {
@@ -84,13 +87,30 @@ class ParameterNameAndTypeCompletion(
error("Not in JetFile")
}
public fun addAll(parameters: CompletionParameters, indicesHelper: KotlinIndicesHelper) {
public fun addFromAllClasses(parameters: CompletionParameters, indicesHelper: KotlinIndicesHelper) {
if (prefixMatcher.getPrefix().isEmpty()) return
AllClassesCompletion(parameters, indicesHelper, modifiedPrefixMatcher, { !it.isSingleton() })
.collect({ addSuggestionsForClassifier(it) }, { addSuggestionsForJavaClass(it) })
}
public fun addFromParametersInFile(position: PsiElement, resolutionFacade: ResolutionFacade, visibilityFilter: (DeclarationDescriptor) -> Boolean) {
position.getContainingFile().forEachDescendantOfType<JetParameter>(
canGoInside = { it !is JetExpression || it is JetDeclaration } // we analyze parameters inside bodies to not resolve too much
) { declaration ->
val name = declaration.getName()
if (name != null && prefixMatcher.prefixMatches(name)) {
val parameter = resolutionFacade.analyze(declaration)[BindingContext.VALUE_PARAMETER, declaration]
if (parameter != null) {
val parameterType = parameter.getType()
if (parameterType.isVisible(visibilityFilter)) {
addLookupElement(NameAndArbitraryType(name, parameterType))
}
}
}
}
}
private fun addSuggestionsForClassifier(classifier: DeclarationDescriptor) {
addSuggestions(classifier.getName().asString()) { name -> NameAndDescriptorType(name, classifier as ClassifierDescriptor) }
}
@@ -103,52 +123,76 @@ class ParameterNameAndTypeCompletion(
val parameterNames = JetNameSuggester.getCamelNames(className, EmptyValidator)
for (parameterName in parameterNames) {
if (prefixMatcher.prefixMatches(parameterName)) {
val nameAndType = nameAndTypeFactory(parameterName)
collector.addElement(MyLookupElement(nameAndType, lookupElementFactory).suppressAutoInsertion())
addLookupElement(nameAndTypeFactory(parameterName))
}
}
}
private interface NameAndType {
val parameterName: String
fun createTypeLookupElement(lookupElementFactory: LookupElementFactory): LookupElement
private fun addLookupElement(nameAndType: NameAndType) {
val lookupElement = MyLookupElement.create(nameAndType, lookupElementFactory)
if (lookupElement != null) {
collector.addElement(lookupElement)
}
}
private data class NameAndDescriptorType(override val parameterName: String, val type: ClassifierDescriptor) : NameAndType {
private fun JetType.isVisible(visibilityFilter: (DeclarationDescriptor) -> Boolean): Boolean {
if (isError()) return false
val classifier = getConstructor().getDeclarationDescriptor() ?: return false
return visibilityFilter(classifier) && getArguments().all { it.getType().isVisible(visibilityFilter) }
}
private abstract class NameAndType(val parameterName: String) {
abstract fun createTypeLookupElement(lookupElementFactory: LookupElementFactory): LookupElement?
}
private class NameAndDescriptorType(parameterName: String, private val type: ClassifierDescriptor) : NameAndType(parameterName) {
override fun createTypeLookupElement(lookupElementFactory: LookupElementFactory)
= lookupElementFactory.createLookupElement(type, false)
}
private data class NameAndJavaType(override val parameterName: String, val type: PsiClass) : NameAndType {
private class NameAndJavaType(parameterName: String, private val type: PsiClass) : NameAndType(parameterName) {
override fun createTypeLookupElement(lookupElementFactory: LookupElementFactory)
= lookupElementFactory.createLookupElementForJavaClass(type)
}
private class MyLookupElement(
val nameAndType: NameAndType,
factory: LookupElementFactory
) : LookupElementDecorator<LookupElement>(nameAndType.createTypeLookupElement(factory)) {
private class NameAndArbitraryType(parameterName: String, private val type: JetType) : NameAndType(parameterName) {
override fun createTypeLookupElement(lookupElementFactory: LookupElementFactory)
= lookupElementFactory.createLookupElementForType(type)
}
private class MyLookupElement private constructor(
private val parameterName: String,
typeLookupElement: LookupElement
) : LookupElementDecorator<LookupElement>(typeLookupElement) {
companion object {
fun create(nameAndType: NameAndType, factory: LookupElementFactory): LookupElement? {
val lookupElement = nameAndType.createTypeLookupElement(factory) ?: return null
return MyLookupElement(nameAndType.parameterName, lookupElement).suppressAutoInsertion()
}
}
override fun equals(other: Any?)
= other is MyLookupElement && nameAndType.parameterName == other.nameAndType.parameterName && getDelegate() == other.getDelegate()
override fun hashCode() = nameAndType.parameterName.hashCode()
= other is MyLookupElement && parameterName == other.parameterName && getDelegate() == other.getDelegate()
override fun hashCode() = parameterName.hashCode()
override fun getLookupString() = nameAndType.parameterName
override fun getAllLookupStrings() = setOf(nameAndType.parameterName)
override fun getLookupString() = parameterName
override fun getAllLookupStrings() = setOf(parameterName)
override fun renderElement(presentation: LookupElementPresentation) {
super.renderElement(presentation)
presentation.setItemText(nameAndType.parameterName + ": " + presentation.getItemText())
presentation.setItemText(parameterName + ": " + presentation.getItemText())
}
override fun handleInsert(context: InsertionContext) {
super.handleInsert(context)
PsiDocumentManager.getInstance(context.getProject()).doPostponedOperationsAndUnblockDocument(context.getDocument())
val settings = CodeStyleSettingsManager.getInstance(context.getProject()).getCurrentSettings().getCustomSettings(javaClass<JetCodeStyleSettings>())
val spaceBefore = if (settings.SPACE_BEFORE_TYPE_COLON) " " else ""
val spaceAfter = if (settings.SPACE_AFTER_TYPE_COLON) " " else ""
val text = nameAndType.parameterName + spaceBefore + ":" + spaceAfter
val text = parameterName + spaceBefore + ":" + spaceAfter
context.getDocument().insertString(context.getStartOffset(), text)
}
}
@@ -371,7 +371,7 @@ class SmartCompletion(
val items = ArrayList<LookupElement>()
for ((type, infos) in expectedInfosGrouped) {
val lookupElement = lookupElementForType(type) ?: continue
val lookupElement = lookupElementFactory.createLookupElementForType(type) ?: continue
items.add(lookupElement.addTailAndNameSimilarity(infos))
}
return Result(null, items, null)
@@ -439,34 +439,6 @@ class SmartCompletion(
return Result(::filterDeclaration, listOf(), null)
}
private fun lookupElementForType(type: JetType): LookupElement? {
if (type.isError()) return null
val classifier = type.getConstructor().getDeclarationDescriptor() ?: return null
val lookupElement = lookupElementFactory.createLookupElement(classifier, bindingContext, false)
var itemText = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(type)
val typeText = IdeDescriptorRenderers.SOURCE_CODE.renderType(type)
val insertHandler: InsertHandler<LookupElement> = object : InsertHandler<LookupElement> {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
context.getDocument().replaceString(context.getStartOffset(), context.getTailOffset(), typeText)
context.setTailOffset(context.getStartOffset() + typeText.length())
shortenReferences(context, context.getStartOffset(), context.getTailOffset())
}
}
return object: LookupElementDecorator<LookupElement>(lookupElement) {
override fun renderElement(presentation: LookupElementPresentation) {
getDelegate().renderElement(presentation)
presentation.setItemText(itemText)
}
override fun handleInsert(context: InsertionContext) {
insertHandler.handleInsert(context, getDelegate())
}
}
}
companion object {
public val OLD_ARGUMENTS_REPLACEMENT_OFFSET: OffsetKey = OffsetKey.create("nonFunctionReplacementOffset")
public val MULTIPLE_ARGUMENTS_REPLACEMENT_OFFSET: OffsetKey = OffsetKey.create("multipleArgumentsReplacementOffset")
@@ -23,6 +23,7 @@ import com.intellij.codeInsight.lookup.LookupElementPresentation
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.completion.ExpectedInfo
import org.jetbrains.kotlin.idea.completion.LookupElementFactory
import org.jetbrains.kotlin.idea.completion.shortenReferences
import org.jetbrains.kotlin.idea.core.isVisible
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.fuzzyReturnType
@@ -52,11 +52,6 @@ class ArtificialElementInsertHandler(
}
}
fun shortenReferences(context: InsertionContext, startOffset: Int, endOffset: Int) {
PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments();
ShortenReferences.DEFAULT.process(context.getFile() as JetFile, startOffset, endOffset)
}
fun mergeTails(tails: Collection<Tail?>): Tail? {
if (tails.size() == 1) return tails.single()
return if (HashSet(tails).size() == 1) tails.first() else null
@@ -0,0 +1,28 @@
import java.io.*
class X {
fun f1(strings: List<String>) { }
fun f2(numbers: List<Int>) { }
}
fun f3(strings: List<String>) { }
fun f4(value: Any?) { }
fun f5(value: File) { }
fun f6(handler: (() -> String)?) { }
class C(val handler: () -> Unit) {
companion object {
fun foo(<caret>) {
fun local(localParam: String/* it should not be included by performance reasons*/){}
}
}
}
// EXIST: { lookupString: "strings", itemText: "strings: List<String>", tailText: " (kotlin)" }
// EXIST: { lookupString: "numbers", itemText: "numbers: List<Int>", tailText: " (kotlin)" }
// EXIST: { lookupString: "value", itemText: "value: Any?", tailText: " (kotlin)" }
// EXIST_JAVA_ONLY: { lookupString: "value", itemText: "value: File", tailText: " (java.io)" }
// EXIST: { lookupString: "handler", itemText: "handler: (() -> String)?", tailText: null }
// EXIST: { lookupString: "handler", itemText: "handler: () -> Unit", tailText: null }
// ABSENT: "localParam"
// ABSENT: "file"
@@ -0,0 +1,19 @@
package ppp
import java.io.*
class X {
public class PublicNested
private class PrivateNested
fun f1(nested: PublicNested) { }
fun f2(nested: PrivateNested) { }
fun f3(nestedList: List<PublicNested>) { }
fun f4(nestedList: List<PrivateNested>) { }
}
fun foo(neste<caret>)
// EXIST: { lookupString: "nested", itemText: "nested: X.PublicNested", tailText: " (ppp)" }
// EXIST: { lookupString: "nestedList", itemText: "nestedList: List<X.PublicNested>", tailText: " (kotlin)" }
// NOTHING_ELSE
@@ -0,0 +1,5 @@
fun foo(xxx: ((java.io.File) -> List<String>)?)
fun bar(x<caret>)
// ELEMENT: xxx
@@ -0,0 +1,7 @@
import java.io.File
fun foo(xxx: ((java.io.File) -> List<String>)?)
fun bar(xxx: ((File) -> List<String>)?<caret>)
// ELEMENT: xxx
+2 -2
View File
@@ -6,6 +6,6 @@ fun bar(o: Any) {
foo(o as <caret>)
}
// EXIST: { lookupString:"List", itemText:"List<String>" }
// EXIST: { lookupString:"Map", itemText:"Map<String, Int>" }
// EXIST: { lookupString:"List", itemText:"List<String>", tailText: " (kotlin)" }
// EXIST: { lookupString:"Map", itemText:"Map<String, Int>", tailText: " (kotlin)" }
// NOTHING_ELSE
+2 -2
View File
@@ -7,6 +7,6 @@ fun bar(o: Any) {
foo(o as <caret>)
}
// EXIST: { lookupString:"List", itemText:"List<String>" }
// EXIST: { lookupString:"String", itemText:"String" }
// EXIST: { lookupString:"List", itemText:"List<String>", tailText: " (kotlin)" }
// EXIST: { lookupString:"String", itemText:"String", tailText: " (kotlin)" }
// NOTHING_ELSE
@@ -1458,6 +1458,18 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
doTest(fileName);
}
@TestMetadata("ParametersInFile.kt")
public void testParametersInFile() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/parameterNameAndType/ParametersInFile.kt");
doTest(fileName);
}
@TestMetadata("ParametersInFileInaccessibleType.kt")
public void testParametersInFileInaccessibleType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/parameterNameAndType/ParametersInFileInaccessibleType.kt");
doTest(fileName);
}
@TestMetadata("Simple.kt")
public void testSimple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/parameterNameAndType/Simple.kt");
@@ -1458,6 +1458,18 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
doTest(fileName);
}
@TestMetadata("ParametersInFile.kt")
public void testParametersInFile() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/parameterNameAndType/ParametersInFile.kt");
doTest(fileName);
}
@TestMetadata("ParametersInFileInaccessibleType.kt")
public void testParametersInFileInaccessibleType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/parameterNameAndType/ParametersInFileInaccessibleType.kt");
doTest(fileName);
}
@TestMetadata("Simple.kt")
public void testSimple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/parameterNameAndType/Simple.kt");
@@ -277,6 +277,12 @@ public class BasicCompletionHandlerTestGenerated extends AbstractBasicCompletion
doTest(fileName);
}
@TestMetadata("ParameterInFile.kt")
public void testParameterInFile() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/parameterNameAndType/ParameterInFile.kt");
doTest(fileName);
}
@TestMetadata("Simple.kt")
public void testSimple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/parameterNameAndType/Simple.kt");