Completion to complete non-imported classes on the first invocation

#KT-8527 Fixed
This commit is contained in:
Valentin Kipyatkov
2016-04-25 17:29:17 +03:00
parent f0dd70d94c
commit deb75b3f18
16 changed files with 88 additions and 49 deletions
@@ -25,7 +25,9 @@ import org.jetbrains.kotlin.asJava.KtLightClass
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper
import org.jetbrains.kotlin.idea.core.isJavaClassNotToBeUsedInKotlin
import org.jetbrains.kotlin.idea.project.ProjectStructureUtil import org.jetbrains.kotlin.idea.project.ProjectStructureUtil
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.load.java.JvmAnnotationNames import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
@@ -38,7 +40,8 @@ class AllClassesCompletion(private val parameters: CompletionParameters,
private val kotlinIndicesHelper: KotlinIndicesHelper, private val kotlinIndicesHelper: KotlinIndicesHelper,
private val prefixMatcher: PrefixMatcher, private val prefixMatcher: PrefixMatcher,
private val resolutionFacade: ResolutionFacade, private val resolutionFacade: ResolutionFacade,
private val kindFilter: (ClassKind) -> Boolean private val kindFilter: (ClassKind) -> Boolean,
private val includeJavaClassesNotToBeUsed: Boolean
) { ) {
fun collect(classDescriptorCollector: (ClassDescriptor) -> Unit, javaClassCollector: (PsiClass) -> Unit) { fun collect(classDescriptorCollector: (ClassDescriptor) -> Unit, javaClassCollector: (PsiClass) -> Unit) {
@@ -84,7 +87,7 @@ class AllClassesCompletion(private val parameters: CompletionParameters,
psiClass.isEnum -> ClassKind.ENUM_CLASS psiClass.isEnum -> ClassKind.ENUM_CLASS
else -> ClassKind.CLASS else -> ClassKind.CLASS
} }
if (kindFilter(kind)) { if (kindFilter(kind) && !isNotToBeUsed(psiClass)) {
collector(psiClass) collector(psiClass)
} }
} }
@@ -97,4 +100,10 @@ class AllClassesCompletion(private val parameters: CompletionParameters,
return (metadata?.findAttributeValue(JvmAnnotationNames.KIND_FIELD_NAME) as? PsiLiteral)?.value == return (metadata?.findAttributeValue(JvmAnnotationNames.KIND_FIELD_NAME) as? PsiLiteral)?.value ==
KotlinClassHeader.Kind.SYNTHETIC_CLASS.id KotlinClassHeader.Kind.SYNTHETIC_CLASS.id
} }
private fun isNotToBeUsed(javaClass: PsiClass): Boolean {
if (includeJavaClassesNotToBeUsed) return false
val fqName = javaClass.getKotlinFqName()
return fqName != null && isJavaClassNotToBeUsedInKotlin(fqName)
}
} }
@@ -16,16 +16,15 @@
package org.jetbrains.kotlin.idea.completion package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.completion.impl.BetterPrefixMatcher
import com.intellij.codeInsight.completion.CompletionSorter
import com.intellij.codeInsight.completion.CompletionType
import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.codeInsight.template.TemplateManager import com.intellij.codeInsight.template.TemplateManager
import com.intellij.patterns.PatternCondition import com.intellij.patterns.PatternCondition
import com.intellij.patterns.StandardPatterns import com.intellij.patterns.StandardPatterns
import com.intellij.psi.JavaPsiFacade import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiClass
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.ProcessingContext import com.intellij.util.ProcessingContext
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
@@ -210,7 +209,7 @@ class BasicCompletionSession(
packageNames.forEach { collector.addElement(basicLookupElementFactory.createLookupElementForPackage(it)) } packageNames.forEach { collector.addElement(basicLookupElementFactory.createLookupElementForPackage(it)) }
} }
flushToResultSet() flushToResultSet()
NamedArgumentCompletion.complete(collector, expectedInfos, callTypeAndReceiver.callType) NamedArgumentCompletion.complete(collector, expectedInfos, callTypeAndReceiver.callType)
@@ -271,7 +270,7 @@ class BasicCompletionSession(
} }
} }
if (callTypeAndReceiver.receiver == null) { if (callTypeAndReceiver.receiver == null && prefix.isNotEmpty()) {
val classKindFilter: ((ClassKind) -> Boolean)? val classKindFilter: ((ClassKind) -> Boolean)?
when (callTypeAndReceiver) { when (callTypeAndReceiver) {
is CallTypeAndReceiver.ANNOTATION -> classKindFilter = { it == ClassKind.ANNOTATION_CLASS } is CallTypeAndReceiver.ANNOTATION -> classKindFilter = { it == ClassKind.ANNOTATION_CLASS }
@@ -279,12 +278,11 @@ class BasicCompletionSession(
else -> classKindFilter = null else -> classKindFilter = null
} }
if (classKindFilter != null) { if (classKindFilter != null) {
if (configuration.completeNonImportedClasses) { val prefixMatcher = if (configuration.useBetterPrefixMatcherForNonImportedClasses)
addClassesFromIndex(classKindFilter) BetterPrefixMatcher(prefixMatcher, collector.bestMatchingDegree)
} else
else { prefixMatcher
collector.advertiseSecondCompletion() addClassesFromIndex(classKindFilter, prefixMatcher)
}
} }
} }
} }
@@ -549,4 +547,16 @@ class BasicCompletionSession(
else -> return null else -> return null
} }
} }
private fun addClassesFromIndex(kindFilter: (ClassKind) -> Boolean, prefixMatcher: PrefixMatcher) {
val classDescriptorCollector = { descriptor: ClassDescriptor ->
collector.addElement(basicLookupElementFactory.createLookupElement(descriptor), notImported = true)
}
val javaClassCollector = { javaClass: PsiClass ->
collector.addElement(basicLookupElementFactory.createLookupElementForJavaClass(javaClass), notImported = true)
}
AllClassesCompletion(parameters, indicesHelper(true), prefixMatcher, resolutionFacade, kindFilter, configuration.completeJavaClassesNotToBeUsed)
.collect(classDescriptorCollector, javaClassCollector)
}
} }
@@ -50,7 +50,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import java.util.* import java.util.*
class CompletionSessionConfiguration( class CompletionSessionConfiguration(
val completeNonImportedClasses: Boolean, val useBetterPrefixMatcherForNonImportedClasses: Boolean,
val completeNonAccessibleDeclarations: Boolean, val completeNonAccessibleDeclarations: Boolean,
val filterOutJavaGettersAndSetters: Boolean, val filterOutJavaGettersAndSetters: Boolean,
val completeJavaClassesNotToBeUsed: Boolean, val completeJavaClassesNotToBeUsed: Boolean,
@@ -58,7 +58,7 @@ class CompletionSessionConfiguration(
) )
fun CompletionSessionConfiguration(parameters: CompletionParameters) = CompletionSessionConfiguration( fun CompletionSessionConfiguration(parameters: CompletionParameters) = CompletionSessionConfiguration(
completeNonImportedClasses = parameters.invocationCount >= 2, useBetterPrefixMatcherForNonImportedClasses = parameters.invocationCount < 2,
completeNonAccessibleDeclarations = parameters.invocationCount >= 2, completeNonAccessibleDeclarations = parameters.invocationCount >= 2,
filterOutJavaGettersAndSetters = parameters.invocationCount < 2, filterOutJavaGettersAndSetters = parameters.invocationCount < 2,
completeJavaClassesNotToBeUsed = parameters.invocationCount >= 2, completeJavaClassesNotToBeUsed = parameters.invocationCount >= 2,
@@ -181,8 +181,7 @@ abstract class CompletionSession(
private fun isVisibleDescriptor(descriptor: DeclarationDescriptor, completeNonAccessible: Boolean): Boolean { private fun isVisibleDescriptor(descriptor: DeclarationDescriptor, completeNonAccessible: Boolean): Boolean {
if (!configuration.completeJavaClassesNotToBeUsed && descriptor is ClassDescriptor) { if (!configuration.completeJavaClassesNotToBeUsed && descriptor is ClassDescriptor) {
val classification = descriptor.importableFqName?.let { importableFqNameClassifier.classify(it, isPackage = false) } if (descriptor.importableFqName?.let { isJavaClassNotToBeUsedInKotlin(it) } == true) return false
if (classification == ImportableFqNameClassifier.Classification.notToBeUsedInKotlin) return false
} }
if (descriptor is TypeParameterDescriptor && !isTypeParameterVisible(descriptor)) return false if (descriptor is TypeParameterDescriptor && !isTypeParameterVisible(descriptor)) return false
@@ -413,14 +412,6 @@ abstract class CompletionSession(
&& this !is CallTypeAndReceiver.IMPORT_DIRECTIVE && this !is CallTypeAndReceiver.IMPORT_DIRECTIVE
} }
protected fun addClassesFromIndex(kindFilter: (ClassKind) -> Boolean) {
AllClassesCompletion(parameters, indicesHelper(true), prefixMatcher, resolutionFacade, kindFilter)
.collect(
{ descriptor -> collector.addElement(basicLookupElementFactory.createLookupElement(descriptor), notImported = true) },
{ javaClass -> collector.addElement(basicLookupElementFactory.createLookupElementForJavaClass(javaClass), notImported = true) }
)
}
protected fun withCollectRequiredContextVariableTypes(action: (LookupElementFactory) -> Unit): Collection<FuzzyType> { protected fun withCollectRequiredContextVariableTypes(action: (LookupElementFactory) -> Unit): Collection<FuzzyType> {
val provider = CollectRequiredTypesContextVariablesProvider() val provider = CollectRequiredTypesContextVariablesProvider()
val lookupElementFactory = createLookupElementFactory(provider) val lookupElementFactory = createLookupElementFactory(provider)
@@ -309,7 +309,7 @@ class KotlinCompletionContributor : CompletionContributor() {
if (!somethingAdded && parameters.invocationCount < 2) { if (!somethingAdded && parameters.invocationCount < 2) {
// Rerun completion if nothing was found // Rerun completion if nothing was found
val newConfiguration = CompletionSessionConfiguration( val newConfiguration = CompletionSessionConfiguration(
completeNonImportedClasses = true, useBetterPrefixMatcherForNonImportedClasses = false,
completeNonAccessibleDeclarations = false, completeNonAccessibleDeclarations = false,
filterOutJavaGettersAndSetters = false, filterOutJavaGettersAndSetters = false,
completeJavaClassesNotToBeUsed = false, completeJavaClassesNotToBeUsed = false,
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.idea.completion package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.completion.* import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.completion.impl.RealPrefixMatchingWeigher
import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementDecorator import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
@@ -34,6 +35,9 @@ class LookupElementsCollector(
sorter: CompletionSorter sorter: CompletionSorter
) { ) {
var bestMatchingDegree = Int.MIN_VALUE
private set
private val elements = ArrayList<LookupElement>() private val elements = ArrayList<LookupElement>()
private val resultSet = resultSet private val resultSet = resultSet
@@ -42,17 +46,22 @@ class LookupElementsCollector(
private val postProcessors = ArrayList<(LookupElement) -> LookupElement>() private val postProcessors = ArrayList<(LookupElement) -> LookupElement>()
var isResultEmpty: Boolean = true
private set
fun flushToResultSet() { fun flushToResultSet() {
if (!elements.isEmpty()) { if (!elements.isEmpty()) {
resultSet.addAllElements(elements) resultSet.addAllElements(elements)
elements.clear() elements.clear()
isResultEmpty = false isResultEmpty = false
for (element in elements) {
val matchingDegree = RealPrefixMatchingWeigher.getBestMatchingDegree(element, prefixMatcher)
bestMatchingDegree = Math.max(bestMatchingDegree, matchingDegree)
}
} }
} }
var isResultEmpty: Boolean = true
private set
fun addLookupElementPostProcessor(processor: (LookupElement) -> LookupElement) { fun addLookupElementPostProcessor(processor: (LookupElement) -> LookupElement) {
postProcessors.add(processor) postProcessors.add(processor)
} }
@@ -142,10 +151,6 @@ class LookupElementsCollector(
elements.forEach { addElement(it, notImported) } elements.forEach { addElement(it, notImported) }
} }
fun advertiseSecondCompletion() {
JavaCompletionContributor.advertiseSecondCompletion(completionParameters.originalFile.project, resultSet)
}
fun restartCompletionOnPrefixChange(prefixCondition: ElementPattern<String>) { fun restartCompletionOnPrefixChange(prefixCondition: ElementPattern<String>) {
resultSet.restartCompletionOnPrefixChange(prefixCondition) resultSet.restartCompletionOnPrefixChange(prefixCondition)
} }
@@ -89,7 +89,7 @@ class ParameterNameAndTypeCompletion(
fun addFromAllClasses(parameters: CompletionParameters, indicesHelper: KotlinIndicesHelper) { fun addFromAllClasses(parameters: CompletionParameters, indicesHelper: KotlinIndicesHelper) {
for ((classNameMatcher, userPrefix) in classNamePrefixMatchers.zip(userPrefixes)) { for ((classNameMatcher, userPrefix) in classNamePrefixMatchers.zip(userPrefixes)) {
AllClassesCompletion( AllClassesCompletion(
parameters, indicesHelper, classNameMatcher, resolutionFacade, { !it.isSingleton } parameters, indicesHelper, classNameMatcher, resolutionFacade, { !it.isSingleton }, includeJavaClassesNotToBeUsed = false
).collect( ).collect(
{ addSuggestionsForClassifier(it, userPrefix, notImported = true) }, { addSuggestionsForClassifier(it, userPrefix, notImported = true) },
{ addSuggestionsForJavaClass(it, userPrefix, notImported = true) } { addSuggestionsForJavaClass(it, userPrefix, notImported = true) }
@@ -8,4 +8,3 @@ class Test {
// EXIST: StringBuilder // EXIST: StringBuilder
// EXIST_JAVA_ONLY: StringBuffer // EXIST_JAVA_ONLY: StringBuffer
// ABSENT: HTMLStyleElement // ABSENT: HTMLStyleElement
// ABSENT: Statement
@@ -9,7 +9,7 @@ class StrSome {
} }
class StrMore { class StrMore {
class StrAbsent class StrInner
} }
// INVOCATION_COUNT: 1 // INVOCATION_COUNT: 1
@@ -19,4 +19,4 @@ class StrMore {
// EXIST: StrInFun // EXIST: StrInFun
// EXIST: StringBuilder // EXIST: StringBuilder
// EXIST_JAVA_ONLY: StringBuffer // EXIST_JAVA_ONLY: StringBuffer
// ABSENT: StrAbsent // EXIST: StrInner
@@ -12,4 +12,3 @@ class SomeClass {
// EXIST: StringBuilder // EXIST: StringBuilder
// EXIST_JAVA_ONLY: StringBuffer // EXIST_JAVA_ONLY: StringBuffer
// ABSENT: HTMLStyleElement // ABSENT: HTMLStyleElement
// ABSENT: Statement
@@ -1,10 +1,10 @@
class Xyz class Xyzzz
fun foo(xyz: Xyz){} fun foo(xyz: Xyzzzz){}
fun bar(o: Any) { fun bar(o: Any) {
foo(o as Xy<caret>) foo(o as Xyz<caret>)
} }
// EXIST: Xyz // EXIST: Xyzzz
// NOTHING_ELSE // NOTHING_ELSE
@@ -0,0 +1,7 @@
class Host
fun foo(): Ho<caret>
// INVOCATION_COUNT: 1
// EXIST: Host
// ABSENT: UnknownHostException
@@ -0,0 +1,7 @@
class Host
fun foo(): Ho<caret>
// INVOCATION_COUNT: 2
// EXIST: Host
// EXIST: UnknownHostException
@@ -1,8 +1,7 @@
import java.util.HashMap import java.util.HashMap
fun foo() { fun foo() {
val v = HashMap<String, <caret> val v = HashMap<String, H<caret>
} }
// INVOCATION_COUNT: 2
// ELEMENT: HashSet // ELEMENT: HashSet
@@ -5,5 +5,4 @@ fun foo() {
val v = HashMap<String, HashSet<caret> val v = HashMap<String, HashSet<caret>
} }
// INVOCATION_COUNT: 2
// ELEMENT: HashSet // ELEMENT: HashSet
@@ -2632,6 +2632,18 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
doTest(fileName); doTest(fileName);
} }
@TestMetadata("NonImportedPrefixMatching1.kt")
public void testNonImportedPrefixMatching1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/java/NonImportedPrefixMatching1.kt");
doTest(fileName);
}
@TestMetadata("NonImportedPrefixMatching2.kt")
public void testNonImportedPrefixMatching2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/java/NonImportedPrefixMatching2.kt");
doTest(fileName);
}
@TestMetadata("Number.kt") @TestMetadata("Number.kt")
public void testNumber() throws Exception { public void testNumber() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/java/Number.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/java/Number.kt");
@@ -67,8 +67,7 @@ class ImportableFqNameClassifier(private val file: KtFile) {
} }
return when { return when {
JavaToKotlinClassMap.INSTANCE.mapPlatformClass(fqName, DefaultBuiltIns.Instance).isNotEmpty() isJavaClassNotToBeUsedInKotlin(fqName) -> Classification.notToBeUsedInKotlin
|| JavaAnnotationMapper.javaToKotlinNameMap[fqName] != null -> Classification.notToBeUsedInKotlin
fqName.parent() == file.packageFqName -> Classification.fromCurrentPackage fqName.parent() == file.packageFqName -> Classification.fromCurrentPackage
@@ -87,4 +86,7 @@ class ImportableFqNameClassifier(private val file: KtFile) {
private fun isImportedWithPreciseImport(name: FqName) = name in preciseImports private fun isImportedWithPreciseImport(name: FqName) = name in preciseImports
private fun isImportedWithAllUnderImport(name: FqName) = name.parent() in allUnderImports private fun isImportedWithAllUnderImport(name: FqName) = name.parent() in allUnderImports
private fun hasPreciseImportFromPackage(packageName: FqName) = packageName in preciseImportPackages private fun hasPreciseImportFromPackage(packageName: FqName) = packageName in preciseImportPackages
} }
fun isJavaClassNotToBeUsedInKotlin(fqName: FqName): Boolean
= JavaToKotlinClassMap.INSTANCE.mapPlatformClass(fqName, DefaultBuiltIns.Instance).isNotEmpty() || JavaAnnotationMapper.javaToKotlinNameMap[fqName] != null