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.ClassKind
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.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
@@ -38,7 +40,8 @@ class AllClassesCompletion(private val parameters: CompletionParameters,
private val kotlinIndicesHelper: KotlinIndicesHelper,
private val prefixMatcher: PrefixMatcher,
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) {
@@ -84,7 +87,7 @@ class AllClassesCompletion(private val parameters: CompletionParameters,
psiClass.isEnum -> ClassKind.ENUM_CLASS
else -> ClassKind.CLASS
}
if (kindFilter(kind)) {
if (kindFilter(kind) && !isNotToBeUsed(psiClass)) {
collector(psiClass)
}
}
@@ -97,4 +100,10 @@ class AllClassesCompletion(private val parameters: CompletionParameters,
return (metadata?.findAttributeValue(JvmAnnotationNames.KIND_FIELD_NAME) as? PsiLiteral)?.value ==
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
import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.codeInsight.completion.CompletionResultSet
import com.intellij.codeInsight.completion.CompletionSorter
import com.intellij.codeInsight.completion.CompletionType
import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.completion.impl.BetterPrefixMatcher
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.codeInsight.template.TemplateManager
import com.intellij.patterns.PatternCondition
import com.intellij.patterns.StandardPatterns
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiClass
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.ProcessingContext
import org.jetbrains.kotlin.descriptors.ClassDescriptor
@@ -210,7 +209,7 @@ class BasicCompletionSession(
packageNames.forEach { collector.addElement(basicLookupElementFactory.createLookupElementForPackage(it)) }
}
flushToResultSet()
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)?
when (callTypeAndReceiver) {
is CallTypeAndReceiver.ANNOTATION -> classKindFilter = { it == ClassKind.ANNOTATION_CLASS }
@@ -279,12 +278,11 @@ class BasicCompletionSession(
else -> classKindFilter = null
}
if (classKindFilter != null) {
if (configuration.completeNonImportedClasses) {
addClassesFromIndex(classKindFilter)
}
else {
collector.advertiseSecondCompletion()
}
val prefixMatcher = if (configuration.useBetterPrefixMatcherForNonImportedClasses)
BetterPrefixMatcher(prefixMatcher, collector.bestMatchingDegree)
else
prefixMatcher
addClassesFromIndex(classKindFilter, prefixMatcher)
}
}
}
@@ -549,4 +547,16 @@ class BasicCompletionSession(
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.*
class CompletionSessionConfiguration(
val completeNonImportedClasses: Boolean,
val useBetterPrefixMatcherForNonImportedClasses: Boolean,
val completeNonAccessibleDeclarations: Boolean,
val filterOutJavaGettersAndSetters: Boolean,
val completeJavaClassesNotToBeUsed: Boolean,
@@ -58,7 +58,7 @@ class CompletionSessionConfiguration(
)
fun CompletionSessionConfiguration(parameters: CompletionParameters) = CompletionSessionConfiguration(
completeNonImportedClasses = parameters.invocationCount >= 2,
useBetterPrefixMatcherForNonImportedClasses = parameters.invocationCount < 2,
completeNonAccessibleDeclarations = parameters.invocationCount >= 2,
filterOutJavaGettersAndSetters = parameters.invocationCount < 2,
completeJavaClassesNotToBeUsed = parameters.invocationCount >= 2,
@@ -181,8 +181,7 @@ abstract class CompletionSession(
private fun isVisibleDescriptor(descriptor: DeclarationDescriptor, completeNonAccessible: Boolean): Boolean {
if (!configuration.completeJavaClassesNotToBeUsed && descriptor is ClassDescriptor) {
val classification = descriptor.importableFqName?.let { importableFqNameClassifier.classify(it, isPackage = false) }
if (classification == ImportableFqNameClassifier.Classification.notToBeUsedInKotlin) return false
if (descriptor.importableFqName?.let { isJavaClassNotToBeUsedInKotlin(it) } == true) return false
}
if (descriptor is TypeParameterDescriptor && !isTypeParameterVisible(descriptor)) return false
@@ -413,14 +412,6 @@ abstract class CompletionSession(
&& 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> {
val provider = CollectRequiredTypesContextVariablesProvider()
val lookupElementFactory = createLookupElementFactory(provider)
@@ -309,7 +309,7 @@ class KotlinCompletionContributor : CompletionContributor() {
if (!somethingAdded && parameters.invocationCount < 2) {
// Rerun completion if nothing was found
val newConfiguration = CompletionSessionConfiguration(
completeNonImportedClasses = true,
useBetterPrefixMatcherForNonImportedClasses = false,
completeNonAccessibleDeclarations = false,
filterOutJavaGettersAndSetters = false,
completeJavaClassesNotToBeUsed = false,
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.completion.impl.RealPrefixMatchingWeigher
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.openapi.util.TextRange
@@ -34,6 +35,9 @@ class LookupElementsCollector(
sorter: CompletionSorter
) {
var bestMatchingDegree = Int.MIN_VALUE
private set
private val elements = ArrayList<LookupElement>()
private val resultSet = resultSet
@@ -42,17 +46,22 @@ class LookupElementsCollector(
private val postProcessors = ArrayList<(LookupElement) -> LookupElement>()
var isResultEmpty: Boolean = true
private set
fun flushToResultSet() {
if (!elements.isEmpty()) {
resultSet.addAllElements(elements)
elements.clear()
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) {
postProcessors.add(processor)
}
@@ -142,10 +151,6 @@ class LookupElementsCollector(
elements.forEach { addElement(it, notImported) }
}
fun advertiseSecondCompletion() {
JavaCompletionContributor.advertiseSecondCompletion(completionParameters.originalFile.project, resultSet)
}
fun restartCompletionOnPrefixChange(prefixCondition: ElementPattern<String>) {
resultSet.restartCompletionOnPrefixChange(prefixCondition)
}
@@ -89,7 +89,7 @@ class ParameterNameAndTypeCompletion(
fun addFromAllClasses(parameters: CompletionParameters, indicesHelper: KotlinIndicesHelper) {
for ((classNameMatcher, userPrefix) in classNamePrefixMatchers.zip(userPrefixes)) {
AllClassesCompletion(
parameters, indicesHelper, classNameMatcher, resolutionFacade, { !it.isSingleton }
parameters, indicesHelper, classNameMatcher, resolutionFacade, { !it.isSingleton }, includeJavaClassesNotToBeUsed = false
).collect(
{ addSuggestionsForClassifier(it, userPrefix, notImported = true) },
{ addSuggestionsForJavaClass(it, userPrefix, notImported = true) }
@@ -8,4 +8,3 @@ class Test {
// EXIST: StringBuilder
// EXIST_JAVA_ONLY: StringBuffer
// ABSENT: HTMLStyleElement
// ABSENT: Statement
@@ -9,7 +9,7 @@ class StrSome {
}
class StrMore {
class StrAbsent
class StrInner
}
// INVOCATION_COUNT: 1
@@ -19,4 +19,4 @@ class StrMore {
// EXIST: StrInFun
// EXIST: StringBuilder
// EXIST_JAVA_ONLY: StringBuffer
// ABSENT: StrAbsent
// EXIST: StrInner
@@ -12,4 +12,3 @@ class SomeClass {
// EXIST: StringBuilder
// EXIST_JAVA_ONLY: StringBuffer
// ABSENT: HTMLStyleElement
// ABSENT: Statement
@@ -1,10 +1,10 @@
class Xyz
class Xyzzz
fun foo(xyz: Xyz){}
fun foo(xyz: Xyzzzz){}
fun bar(o: Any) {
foo(o as Xy<caret>)
foo(o as Xyz<caret>)
}
// EXIST: Xyz
// EXIST: Xyzzz
// 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
fun foo() {
val v = HashMap<String, <caret>
val v = HashMap<String, H<caret>
}
// INVOCATION_COUNT: 2
// ELEMENT: HashSet
@@ -5,5 +5,4 @@ fun foo() {
val v = HashMap<String, HashSet<caret>
}
// INVOCATION_COUNT: 2
// ELEMENT: HashSet
@@ -2632,6 +2632,18 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
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")
public void testNumber() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/java/Number.kt");
@@ -67,8 +67,7 @@ class ImportableFqNameClassifier(private val file: KtFile) {
}
return when {
JavaToKotlinClassMap.INSTANCE.mapPlatformClass(fqName, DefaultBuiltIns.Instance).isNotEmpty()
|| JavaAnnotationMapper.javaToKotlinNameMap[fqName] != null -> Classification.notToBeUsedInKotlin
isJavaClassNotToBeUsedInKotlin(fqName) -> Classification.notToBeUsedInKotlin
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 isImportedWithAllUnderImport(name: FqName) = name.parent() in allUnderImports
private fun hasPreciseImportFromPackage(packageName: FqName) = packageName in preciseImportPackages
}
}
fun isJavaClassNotToBeUsedInKotlin(fqName: FqName): Boolean
= JavaToKotlinClassMap.INSTANCE.mapPlatformClass(fqName, DefaultBuiltIns.Instance).isNotEmpty() || JavaAnnotationMapper.javaToKotlinNameMap[fqName] != null