Initial implementation of KT-8209 Support static member completion without writing the class name

This commit is contained in:
Valentin Kipyatkov
2015-11-02 18:07:30 +03:00
parent 7be9699263
commit ca437e51c4
21 changed files with 400 additions and 103 deletions
@@ -29,10 +29,10 @@ import java.util.*
class KtClassBody : KtElementImplStub<KotlinPlaceHolderStub<KtClassBody>>, KtDeclarationContainer { class KtClassBody : KtElementImplStub<KotlinPlaceHolderStub<KtClassBody>>, KtDeclarationContainer {
constructor(node: ASTNode) : super(node) constructor(node: ASTNode) : super(node)
override fun getParent() = parentByStub
constructor(stub: KotlinPlaceHolderStub<KtClassBody>) : super(stub, CLASS_BODY) constructor(stub: KotlinPlaceHolderStub<KtClassBody>) : super(stub, CLASS_BODY)
override fun getParent() = parentByStub
override fun getDeclarations() = Arrays.asList(*getStubOrPsiChildren(DECLARATION_TYPES, KtDeclaration.ARRAY_FACTORY)) override fun getDeclarations() = Arrays.asList(*getStubOrPsiChildren(DECLARATION_TYPES, KtDeclaration.ARRAY_FACTORY))
override fun <R, D> accept(visitor: KtVisitor<R, D>, data: D) = visitor.visitClassBody(this, data) override fun <R, D> accept(visitor: KtVisitor<R, D>, data: D) = visitor.visitClassBody(this, data)
@@ -196,7 +196,7 @@ class BasicCompletionSession(
packageNames.forEach { collector.addElement(basicLookupElementFactory.createLookupElementForPackage(it)) } packageNames.forEach { collector.addElement(basicLookupElementFactory.createLookupElementForPackage(it)) }
} }
flushToResultSet() flushToResultSet()
NamedArgumentCompletion.complete(collector, expectedInfos) NamedArgumentCompletion.complete(collector, expectedInfos)
@@ -227,6 +227,10 @@ class BasicCompletionSession(
flushToResultSet() flushToResultSet()
} }
} }
if (configuration.completeStaticMembers && callTypeAndReceiver is CallTypeAndReceiver.DEFAULT && prefix.isNotEmpty()) {
StaticMembersCompletion(collector, prefixMatcher, indicesHelper(false), lookupElementFactory).complete()
}
} }
} }
@@ -410,13 +414,13 @@ class BasicCompletionSession(
lookupElement lookupElement
} }
parameterNameAndTypeCompletion.addFromParametersInFile(position, resolutionFacade, isVisibleFilter) parameterNameAndTypeCompletion.addFromParametersInFile(position, resolutionFacade, isVisibleFilterCheckAlways)
flushToResultSet() flushToResultSet()
parameterNameAndTypeCompletion.addFromImportedClasses(position, bindingContext, isVisibleFilter) parameterNameAndTypeCompletion.addFromImportedClasses(position, bindingContext, isVisibleFilterCheckAlways)
flushToResultSet() flushToResultSet()
parameterNameAndTypeCompletion.addFromAllClasses(parameters, indicesHelper) parameterNameAndTypeCompletion.addFromAllClasses(parameters, indicesHelper(false))
} }
} }
@@ -57,19 +57,23 @@ class CompletionSessionConfiguration(
val completeNonImportedDeclarations: Boolean, val completeNonImportedDeclarations: Boolean,
val completeNonAccessibleDeclarations: Boolean, val completeNonAccessibleDeclarations: Boolean,
val filterOutJavaGettersAndSetters: Boolean, val filterOutJavaGettersAndSetters: Boolean,
val completeJavaClassesNotToBeUsed: Boolean val completeJavaClassesNotToBeUsed: Boolean,
val completeStaticMembers: Boolean
) )
fun CompletionSessionConfiguration(parameters: CompletionParameters) = CompletionSessionConfiguration( fun CompletionSessionConfiguration(parameters: CompletionParameters) = CompletionSessionConfiguration(
completeNonImportedDeclarations = parameters.invocationCount >= 2, completeNonImportedDeclarations = 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,
completeStaticMembers = parameters.invocationCount >= 2
) )
abstract class CompletionSession(protected val configuration: CompletionSessionConfiguration, abstract class CompletionSession(
protected val parameters: CompletionParameters, protected val configuration: CompletionSessionConfiguration,
resultSet: CompletionResultSet) { protected val parameters: CompletionParameters,
resultSet: CompletionResultSet
) {
protected val position = parameters.getPosition() protected val position = parameters.getPosition()
private val file = position.getContainingFile() as KtFile private val file = position.getContainingFile() as KtFile
protected val resolutionFacade = file.getResolutionFacade() protected val resolutionFacade = file.getResolutionFacade()
@@ -124,7 +128,8 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
private fun ((Name) -> Boolean).or(otherFilter: (Name) -> Boolean): (Name) -> Boolean private fun ((Name) -> Boolean).or(otherFilter: (Name) -> Boolean): (Name) -> Boolean
= { this(it) || otherFilter(it) } = { this(it) || otherFilter(it) }
protected val isVisibleFilter: (DeclarationDescriptor) -> Boolean = { isVisibleDescriptor(it) } protected val isVisibleFilter: (DeclarationDescriptor) -> Boolean = { isVisibleDescriptor(it, completeNonAccessible = configuration.completeNonAccessibleDeclarations) }
protected val isVisibleFilterCheckAlways: (DeclarationDescriptor) -> Boolean = { isVisibleDescriptor(it, completeNonAccessible = false) }
protected val referenceVariantsHelper = ReferenceVariantsHelper(bindingContext, resolutionFacade, moduleDescriptor, isVisibleFilter) protected val referenceVariantsHelper = ReferenceVariantsHelper(bindingContext, resolutionFacade, moduleDescriptor, isVisibleFilter)
@@ -151,8 +156,10 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
override fun contains(file: VirtualFile) = super.contains(file) && file != parameters.getOriginalFile().getVirtualFile() override fun contains(file: VirtualFile) = super.contains(file) && file != parameters.getOriginalFile().getVirtualFile()
} }
protected val indicesHelper: KotlinIndicesHelper protected fun indicesHelper(mayIncludeInaccessible: Boolean): KotlinIndicesHelper {
get() = KotlinIndicesHelper(resolutionFacade, searchScope, isVisibleFilter, true) val filter = if (mayIncludeInaccessible) isVisibleFilter else isVisibleFilterCheckAlways
return KotlinIndicesHelper(resolutionFacade, searchScope, filter, visibilityFilterMayIncludeAccessible = mayIncludeInaccessible)
}
protected val toFromOriginalFileMapper: ToFromOriginalFileMapper protected val toFromOriginalFileMapper: ToFromOriginalFileMapper
= ToFromOriginalFileMapper(parameters.originalFile as KtFile, position.containingFile as KtFile, parameters.offset) = ToFromOriginalFileMapper(parameters.originalFile as KtFile, position.containingFile as KtFile, parameters.offset)
@@ -178,7 +185,7 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
get() = 0 get() = 0
} }
private fun isVisibleDescriptor(descriptor: DeclarationDescriptor): 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) } val classification = descriptor.importableFqName?.let { importableFqNameClassifier.classify(it, isPackage = false) }
if (classification == ImportableFqNameClassifier.Classification.notToBeUsedInKotlin) return false if (classification == ImportableFqNameClassifier.Classification.notToBeUsedInKotlin) return false
@@ -189,7 +196,7 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
if (descriptor is DeclarationDescriptorWithVisibility) { if (descriptor is DeclarationDescriptorWithVisibility) {
val visible = descriptor.isVisible(inDescriptor, bindingContext, nameExpression) val visible = descriptor.isVisible(inDescriptor, bindingContext, nameExpression)
if (visible) return true if (visible) return true
return configuration.completeNonAccessibleDeclarations && !descriptor.isFromLibrary() return completeNonAccessible && !descriptor.isFromLibrary()
} }
return true return true
@@ -309,6 +316,7 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
var notImportedExtensions: Collection<CallableDescriptor> = emptyList() var notImportedExtensions: Collection<CallableDescriptor> = emptyList()
if (callTypeAndReceiver.shouldCompleteCallableExtensions()) { if (callTypeAndReceiver.shouldCompleteCallableExtensions()) {
val nameFilter: (String) -> Boolean = { prefixMatcher.prefixMatches(it) } val nameFilter: (String) -> Boolean = { prefixMatcher.prefixMatches(it) }
val indicesHelper = indicesHelper(true)
val extensions = if (runtimeReceiver != null) val extensions = if (runtimeReceiver != null)
indicesHelper.getCallableTopLevelExtensions(callTypeAndReceiver, listOf(runtimeReceiver.type), nameFilter) indicesHelper.getCallableTopLevelExtensions(callTypeAndReceiver, listOf(runtimeReceiver.type), nameFilter)
else else
@@ -384,7 +392,7 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
} }
protected fun getTopLevelCallables(): Collection<DeclarationDescriptor> { protected fun getTopLevelCallables(): Collection<DeclarationDescriptor> {
return indicesHelper.getTopLevelCallables({ prefixMatcher.prefixMatches(it) }) return indicesHelper(true).getTopLevelCallables({ prefixMatcher.prefixMatches(it) })
.filterShadowedNonImported() .filterShadowedNonImported()
} }
@@ -399,7 +407,7 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
} }
protected fun addClassesFromIndex(kindFilter: (ClassKind) -> Boolean) { protected fun addClassesFromIndex(kindFilter: (ClassKind) -> Boolean) {
AllClassesCompletion(parameters, indicesHelper, prefixMatcher, resolutionFacade, kindFilter) AllClassesCompletion(parameters, indicesHelper(true), prefixMatcher, resolutionFacade, kindFilter)
.collect( .collect(
{ descriptor -> collector.addElement(basicLookupElementFactory.createLookupElement(descriptor), notImported = true) }, { descriptor -> collector.addElement(basicLookupElementFactory.createLookupElement(descriptor), notImported = true) },
{ javaClass -> collector.addElement(basicLookupElementFactory.createLookupElementForJavaClass(javaClass), notImported = true) } { javaClass -> collector.addElement(basicLookupElementFactory.createLookupElementForJavaClass(javaClass), notImported = true) }
@@ -27,8 +27,7 @@ import com.intellij.patterns.ElementPattern
import com.intellij.patterns.StandardPatterns import com.intellij.patterns.StandardPatterns
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.idea.KotlinIcons import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.idea.completion.handlers.CastReceiverInsertHandler import org.jetbrains.kotlin.idea.completion.handlers.CastReceiverInsertHandler
import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler
@@ -40,6 +39,7 @@ import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.renderer.render import org.jetbrains.kotlin.renderer.render
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.inline.InlineUtil import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.TypeNullability import org.jetbrains.kotlin.types.typeUtil.TypeNullability
@@ -69,7 +69,8 @@ enum class ItemPriority {
DEFAULT, DEFAULT,
IMPLEMENT, IMPLEMENT,
OVERRIDE, OVERRIDE,
NAMED_PARAMETER NAMED_PARAMETER,
STATIC_MEMBER
} }
val ITEM_PRIORITY_KEY = Key<ItemPriority>("ITEM_PRIORITY_KEY") val ITEM_PRIORITY_KEY = Key<ItemPriority>("ITEM_PRIORITY_KEY")
@@ -152,7 +153,7 @@ fun InsertionContext.isAfterDot(): Boolean {
val chars = getDocument().getCharsSequence() val chars = getDocument().getCharsSequence()
while (offset > 0) { while (offset > 0) {
offset-- offset--
val c = chars.charAt(offset) val c = chars[offset]
if (!Character.isWhitespace(c)) { if (!Character.isWhitespace(c)) {
return c == '.' return c == '.'
} }
@@ -335,7 +336,7 @@ private open class BaseTypeLookupElement(type: KotlinType, baseLookupElement: Lo
override fun handleInsert(context: InsertionContext) { override fun handleInsert(context: InsertionContext) {
context.getDocument().replaceString(context.getStartOffset(), context.getTailOffset(), fullText) context.getDocument().replaceString(context.getStartOffset(), context.getTailOffset(), fullText)
context.setTailOffset(context.getStartOffset() + fullText.length()) context.setTailOffset(context.getStartOffset() + fullText.length)
shortenReferences(context, context.getStartOffset(), context.getTailOffset()) shortenReferences(context, context.getStartOffset(), context.getTailOffset())
} }
} }
@@ -350,3 +351,49 @@ fun <T> ElementPattern<T>.andNot(rhs: ElementPattern<T>) = StandardPatterns.and(
fun <T> ElementPattern<T>.or(rhs: ElementPattern<T>) = StandardPatterns.or(this, rhs) fun <T> ElementPattern<T>.or(rhs: ElementPattern<T>) = StandardPatterns.or(this, rhs)
fun singleCharPattern(char: Char) = StandardPatterns.character().equalTo(char) fun singleCharPattern(char: Char) = StandardPatterns.character().equalTo(char)
fun LookupElement.decorateAsStaticMember(
memberDescriptor: DeclarationDescriptor,
classDescriptor: ClassDescriptor,
classNameAsLookupString: Boolean
): LookupElement {
val qualifierPresentation = classDescriptor.getName().asString()
val qualifierText = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(classDescriptor)
return object: LookupElementDecorator<LookupElement>(this) {
override fun getAllLookupStrings(): Set<String> {
return if (classNameAsLookupString) setOf(delegate.lookupString, qualifierPresentation) else super.getAllLookupStrings()
}
override fun renderElement(presentation: LookupElementPresentation) {
delegate.renderElement(presentation)
presentation.itemText = qualifierPresentation + "." + presentation.itemText
val tailText = " (" + DescriptorUtils.getFqName(classDescriptor.containingDeclaration) + ")"
if (memberDescriptor is FunctionDescriptor) {
presentation.appendTailText(tailText, true)
}
else {
presentation.setTailText(tailText, true)
}
if (presentation.typeText.isNullOrEmpty()) {
presentation.typeText = DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(classDescriptor.defaultType)
}
}
override fun handleInsert(context: InsertionContext) {
val prefix = qualifierText + "."
val offset = context.startOffset
context.document.insertString(offset, prefix)
context.offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, offset + prefix.length)
shortenReferences(context, offset, offset + prefix.length)
PsiDocumentManager.getInstance(context.project).doPostponedOperationsAndUnblockDocument(context.document)
super.handleInsert(context)
}
}
}
@@ -257,13 +257,15 @@ public class KotlinCompletionContributor : CompletionContributor() {
} }
val somethingAdded = session.complete() val somethingAdded = session.complete()
if (!somethingAdded && parameters.getInvocationCount() < 2) { if (!somethingAdded && parameters.invocationCount < 2) {
// Rerun completion if nothing was found // Rerun completion if nothing was found
val newConfiguration = CompletionSessionConfiguration( val newConfiguration = CompletionSessionConfiguration(
completeNonImportedDeclarations = true, completeNonImportedDeclarations = true,
completeNonAccessibleDeclarations = false, completeNonAccessibleDeclarations = false,
filterOutJavaGettersAndSetters = false, filterOutJavaGettersAndSetters = false,
completeJavaClassesNotToBeUsed = false) completeJavaClassesNotToBeUsed = false,
completeStaticMembers = parameters.invocationCount > 0
)
BasicCompletionSession(newConfiguration, parameters, result).complete() BasicCompletionSession(newConfiguration, parameters, result).complete()
} }
} }
@@ -0,0 +1,60 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.codeInsight.lookup.LookupElement
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindExclude
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
class StaticMembersCompletion(
private val collector: LookupElementsCollector,
private val prefixMatcher: PrefixMatcher,
private val indicesHelper: KotlinIndicesHelper,
private val lookupElementFactory: LookupElementFactory
) {
//TODO: SAM-adapters
//TODO: different priority&behaviour when static import from the same class exist
//TODO: filter out those that are visible from bases/imports etc
//TODO: filter out those that are accessible from SmartCompletion.additionalItems
fun complete() {
val descriptorKindFilter = DescriptorKindFilter.CALLABLES exclude DescriptorKindExclude.Extensions
val nameFilter: (String) -> Boolean = { prefixMatcher.prefixMatches(it) }
for (member in indicesHelper.getJavaStaticMembers(descriptorKindFilter, nameFilter)) {
collector.addElement(createLookupElement(member) ?: continue)
}
for (member in indicesHelper.getObjectMembers(descriptorKindFilter, nameFilter)) {
collector.addElement(createLookupElement(member) ?: continue)
}
}
private fun createLookupElement(descriptor: CallableDescriptor): LookupElement? {
var classDescriptor = descriptor.containingDeclaration as? ClassDescriptor ?: return null
if (classDescriptor.isCompanionObject) {
classDescriptor = classDescriptor.containingDeclaration as? ClassDescriptor ?: return null
}
return lookupElementFactory.createLookupElement(descriptor, useReceiverTypes = false)
.decorateAsStaticMember(descriptor, classDescriptor, classNameAsLookupString = false)
.assignPriority(ItemPriority.STATIC_MEMBER)
.suppressAutoInsertion()
}
}
@@ -16,24 +16,17 @@
package org.jetbrains.kotlin.idea.completion.smart package org.jetbrains.kotlin.idea.completion.smart
import com.intellij.codeInsight.completion.CompletionInitializationContext
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.codeInsight.lookup.LookupElementPresentation
import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.completion.ExpectedInfo import org.jetbrains.kotlin.idea.completion.ExpectedInfo
import org.jetbrains.kotlin.idea.completion.LookupElementFactory import org.jetbrains.kotlin.idea.completion.LookupElementFactory
import org.jetbrains.kotlin.idea.completion.decorateAsStaticMember
import org.jetbrains.kotlin.idea.completion.fuzzyType import org.jetbrains.kotlin.idea.completion.fuzzyType
import org.jetbrains.kotlin.idea.completion.shortenReferences
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.core.isVisible import org.jetbrains.kotlin.idea.core.isVisible
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.fuzzyReturnType import org.jetbrains.kotlin.idea.util.fuzzyReturnType
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.psi.KtSimpleNameExpression import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
@@ -107,45 +100,10 @@ class StaticMembers(
} }
private fun createLookupElements(memberDescriptor: DeclarationDescriptor, classDescriptor: ClassDescriptor): Collection<LookupElement> { private fun createLookupElements(memberDescriptor: DeclarationDescriptor, classDescriptor: ClassDescriptor): Collection<LookupElement> {
val lookupElements = lookupElementFactory.createLookupElementsInSmartCompletion(memberDescriptor, bindingContext, useReceiverTypes = false) return lookupElementFactory.createLookupElementsInSmartCompletion(memberDescriptor, bindingContext, useReceiverTypes = false)
val qualifierPresentation = classDescriptor.getName().asString() .map {
val qualifierText = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(classDescriptor) it.decorateAsStaticMember(memberDescriptor, classDescriptor, classNameAsLookupString = true)
.assignSmartCompletionPriority(SmartCompletionItemPriority.STATIC_MEMBER)
return lookupElements.map {
object: LookupElementDecorator<LookupElement>(it) {
override fun getAllLookupStrings() = setOf(delegate.lookupString, qualifierPresentation)
override fun renderElement(presentation: LookupElementPresentation) {
getDelegate().renderElement(presentation)
presentation.setItemText(qualifierPresentation + "." + presentation.getItemText())
val tailText = " (" + DescriptorUtils.getFqName(classDescriptor.getContainingDeclaration()) + ")"
if (memberDescriptor is FunctionDescriptor) {
presentation.appendTailText(tailText, true)
}
else {
presentation.setTailText(tailText, true)
}
if (presentation.getTypeText().isNullOrEmpty()) {
presentation.setTypeText(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(classDescriptor.getDefaultType()))
}
} }
override fun handleInsert(context: InsertionContext) {
val prefix = qualifierText + "."
val offset = context.startOffset
context.document.insertString(offset, prefix)
context.offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, offset + prefix.length)
shortenReferences(context, offset, offset + prefix.length)
PsiDocumentManager.getInstance(context.project).doPostponedOperationsAndUnblockDocument(context.document)
super.handleInsert(context)
}
}.assignSmartCompletionPriority(SmartCompletionItemPriority.STATIC_MEMBER)
}
} }
} }
@@ -0,0 +1,8 @@
fun foo() {
SOU<caret>
}
// INVOCATION_COUNT: 2
// EXIST_JAVA_ONLY: { allLookupStrings: "SOUTH, getSOUTH", itemText: "GridBagConstraints.SOUTH", tailText: " (java.awt)", typeText: "Int", attributes: "" }
// EXIST_JAVA_ONLY: { allLookupStrings: "SOUTHWEST, getSOUTHWEST", itemText: "GridBagConstraints.SOUTHWEST", tailText: " (java.awt)", typeText: "Int", attributes: "" }
// ABSENT: serialVersionUID
@@ -0,0 +1,8 @@
fun foo() {
invoke<caret>
}
// INVOCATION_COUNT: 2
// EXIST_JAVA_ONLY: { allLookupStrings: "invokeLater", itemText: "SwingUtilities.invokeLater", tailText: "(Runnable!) (javax.swing)", typeText: "Unit", attributes: "" }
// EXIST_JAVA_ONLY: { allLookupStrings: "invokeAndWait", itemText: "SwingUtilities.invokeAndWait", tailText: "(Runnable!) (javax.swing)", typeText: "Unit", attributes: "" }
// ABSENT: { itemText: "SwingUtilities.convertScreenLocationToParent" }
@@ -0,0 +1,19 @@
package test
object KotlinObject {
fun funFromObject() { }
private fun privateFun(){}
}
class KotlinClass {
companion object SomeName {
fun funFromCompanionObject() { }
private fun privateFun(){}
}
}
class AnotherKotlinClass {
private companion object {
fun funFromPrivateCompanionObject() { }
}
}
@@ -0,0 +1,11 @@
package first
fun testFun() {
funFromO<caret>
}
// INVOCATION_COUNT: 2
// EXIST: { allLookupStrings: "funFromObject", itemText: "KotlinObject.funFromObject", tailText: "() (test)", typeText: "Unit", attributes: "" }
// EXIST: { allLookupStrings: "funFromCompanionObject", itemText: "KotlinClass.funFromCompanionObject", tailText: "() (test)", typeText: "Unit", attributes: "" }
// ABSENT: privateFun
// ABSENT: funFromPrivateCompanionObject
@@ -0,0 +1,6 @@
fun foo() {
invoke<caret>
}
// INVOCATION_COUNT: 2
// ELEMENT_TEXT: "SwingUtilities.invokeLater"
@@ -0,0 +1,8 @@
import javax.swing.SwingUtilities
fun foo() {
SwingUtilities.invokeLater(<caret>)
}
// INVOCATION_COUNT: 2
// ELEMENT_TEXT: "SwingUtilities.invokeLater"
@@ -0,0 +1,6 @@
fun foo() {
fromLit<caret>
}
// INVOCATION_COUNT: 2
// ELEMENT_TEXT: "Regex.fromLiteral"
@@ -0,0 +1,8 @@
import kotlin.text.Regex
fun foo() {
Regex.fromLiteral(<caret>)
}
// INVOCATION_COUNT: 2
// ELEMENT_TEXT: "Regex.fromLiteral"
@@ -2095,6 +2095,27 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
} }
} }
@TestMetadata("idea/idea-completion/testData/basic/common/staticMembers")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class StaticMembers extends AbstractJSBasicCompletionTest {
public void testAllFilesPresentInStaticMembers() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/staticMembers"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("JavaStaticFields.kt")
public void testJavaStaticFields() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticFields.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticMethods.kt")
public void testJavaStaticMethods() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticMethods.kt");
doTest(fileName);
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/substitutedSignature") @TestMetadata("idea/idea-completion/testData/basic/common/substitutedSignature")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -2095,6 +2095,27 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
} }
} }
@TestMetadata("idea/idea-completion/testData/basic/common/staticMembers")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class StaticMembers extends AbstractJvmBasicCompletionTest {
public void testAllFilesPresentInStaticMembers() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/staticMembers"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("JavaStaticFields.kt")
public void testJavaStaticFields() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticFields.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticMethods.kt")
public void testJavaStaticMethods() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticMethods.kt");
doTest(fileName);
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/substitutedSignature") @TestMetadata("idea/idea-completion/testData/basic/common/substitutedSignature")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -281,6 +281,12 @@ public class MultiFileJvmBasicCompletionTestGenerated extends AbstractMultiFileJ
doTest(fileName); doTest(fileName);
} }
@TestMetadata("ObjectMembers")
public void testObjectMembers() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/multifile/ObjectMembers/");
doTest(fileName);
}
@TestMetadata("ParameterNameAndTypeNestedClasses") @TestMetadata("ParameterNameAndTypeNestedClasses")
public void testParameterNameAndTypeNestedClasses() throws Exception { public void testParameterNameAndTypeNestedClasses() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/multifile/ParameterNameAndTypeNestedClasses/"); String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/multifile/ParameterNameAndTypeNestedClasses/");
@@ -530,6 +530,27 @@ public class BasicCompletionHandlerTestGenerated extends AbstractBasicCompletion
} }
} }
@TestMetadata("idea/idea-completion/testData/handlers/basic/staticMembers")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class StaticMembers extends AbstractBasicCompletionHandlerTest {
public void testAllFilesPresentInStaticMembers() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/handlers/basic/staticMembers"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("classObjectMethod.kt")
public void testClassObjectMethod() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/staticMembers/classObjectMethod.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticMethod.kt")
public void testJavaStaticMethod() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/staticMembers/JavaStaticMethod.kt");
doTest(fileName);
}
}
@TestMetadata("idea/idea-completion/testData/handlers/basic/stringTemplate") @TestMetadata("idea/idea-completion/testData/handlers/basic/stringTemplate")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -17,10 +17,16 @@
package org.jetbrains.kotlin.idea.core package org.jetbrains.kotlin.idea.core
import com.intellij.codeInsight.CodeInsightSettings import com.intellij.codeInsight.CodeInsightSettings
import com.intellij.openapi.progress.ProgressManager
import com.intellij.psi.PsiModifier
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.PsiShortNamesCache import com.intellij.psi.search.PsiShortNamesCache
import com.intellij.psi.stubs.StringStubIndexExtension import com.intellij.psi.stubs.StringStubIndexExtension
import com.intellij.util.Processor
import com.intellij.util.indexing.IdFilter
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.getJavaFieldDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getJavaMethodDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
@@ -29,14 +35,16 @@ import org.jetbrains.kotlin.idea.util.CallType
import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver
import org.jetbrains.kotlin.idea.util.receiverTypes import org.jetbrains.kotlin.idea.util.receiverTypes
import org.jetbrains.kotlin.idea.util.substituteExtensionIfCallable import org.jetbrains.kotlin.idea.util.substituteExtensionIfCallable
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtCallableDeclaration import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.isAnnotatedAsHidden import org.jetbrains.kotlin.resolve.isAnnotatedAsHidden
import org.jetbrains.kotlin.resolve.lazy.ResolveSessionUtils import org.jetbrains.kotlin.resolve.lazy.ResolveSessionUtils
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList
@@ -46,7 +54,8 @@ public class KotlinIndicesHelper(
private val resolutionFacade: ResolutionFacade, private val resolutionFacade: ResolutionFacade,
private val scope: GlobalSearchScope, private val scope: GlobalSearchScope,
visibilityFilter: (DeclarationDescriptor) -> Boolean, visibilityFilter: (DeclarationDescriptor) -> Boolean,
applyExcludeSettings: Boolean applyExcludeSettings: Boolean = true,
private val visibilityFilterMayIncludeAccessible: Boolean = false
) { ) {
private val moduleDescriptor = resolutionFacade.moduleDescriptor private val moduleDescriptor = resolutionFacade.moduleDescriptor
@@ -60,20 +69,15 @@ public class KotlinIndicesHelper(
} }
public fun getTopLevelCallablesByName(name: String): Collection<CallableDescriptor> { public fun getTopLevelCallablesByName(name: String): Collection<CallableDescriptor> {
val declarations = HashSet<KtNamedDeclaration>() val declarations = HashSet<KtCallableDeclaration>()
declarations.addTopLevelNonExtensionCallablesByName(KotlinFunctionShortNameIndex.getInstance(), name) declarations.addTopLevelNonExtensionCallablesByName(KotlinFunctionShortNameIndex.getInstance(), name)
declarations.addTopLevelNonExtensionCallablesByName(KotlinPropertyShortNameIndex.getInstance(), name) declarations.addTopLevelNonExtensionCallablesByName(KotlinPropertyShortNameIndex.getInstance(), name)
return declarations.flatMap { return declarations
if (it.getContainingJetFile().isCompiled()) { //TODO: it's temporary while resolveToDescriptor does not work for compiled declarations .flatMap { it.resolveToDescriptorsWithHack() }
resolutionFacade.resolveImportReference(moduleDescriptor, it.getFqName()!!).filterIsInstance<CallableDescriptor>() .filter { it.extensionReceiverParameter == null && descriptorFilter(it) }
}
else {
(resolutionFacade.resolveToDescriptor(it) as? CallableDescriptor).singletonOrEmptyList()
}
}.filter { it.getExtensionReceiverParameter() == null && descriptorFilter(it) }
} }
private fun MutableSet<KtNamedDeclaration>.addTopLevelNonExtensionCallablesByName( private fun MutableSet<KtCallableDeclaration>.addTopLevelNonExtensionCallablesByName(
index: StringStubIndexExtension<out KtCallableDeclaration>, index: StringStubIndexExtension<out KtCallableDeclaration>,
name: String name: String
) { ) {
@@ -84,7 +88,10 @@ public class KotlinIndicesHelper(
return (KotlinTopLevelFunctionFqnNameIndex.getInstance().getAllKeys(project).asSequence() + return (KotlinTopLevelFunctionFqnNameIndex.getInstance().getAllKeys(project).asSequence() +
KotlinTopLevelPropertyFqnNameIndex.getInstance().getAllKeys(project).asSequence()) KotlinTopLevelPropertyFqnNameIndex.getInstance().getAllKeys(project).asSequence())
.map { FqName(it) } .map { FqName(it) }
.filter { nameFilter(it.shortName().asString()) } .filter {
ProgressManager.checkCanceled()
nameFilter(it.shortName().asString())
}
.toSet() .toSet()
.flatMap { findTopLevelCallables(it).filter(descriptorFilter) } .flatMap { findTopLevelCallables(it).filter(descriptorFilter) }
} }
@@ -115,8 +122,9 @@ public class KotlinIndicesHelper(
val declarations = index.getAllKeys(project) val declarations = index.getAllKeys(project)
.asSequence() .asSequence()
.filter { .filter {
ProgressManager.checkCanceled()
KotlinTopLevelExtensionsByReceiverTypeIndex.receiverTypeNameFromKey(it) in receiverTypeNames KotlinTopLevelExtensionsByReceiverTypeIndex.receiverTypeNameFromKey(it) in receiverTypeNames
&& nameFilter(KotlinTopLevelExtensionsByReceiverTypeIndex.callableNameFromKey(it)) && nameFilter(KotlinTopLevelExtensionsByReceiverTypeIndex.callableNameFromKey(it))
} }
.flatMap { index.get(it, project, scope).asSequence() } .flatMap { index.get(it, project, scope).asSequence() }
@@ -140,24 +148,12 @@ public class KotlinIndicesHelper(
val result = LinkedHashSet<CallableDescriptor>() val result = LinkedHashSet<CallableDescriptor>()
fun processDescriptor(descriptor: CallableDescriptor) { fun processDescriptor(descriptor: CallableDescriptor) {
if (descriptorFilter(descriptor)) { if (descriptor.extensionReceiverParameter != null && descriptorFilter(descriptor)) {
result.addAll(descriptor.substituteExtensionIfCallable(receiverTypes, callType)) result.addAll(descriptor.substituteExtensionIfCallable(receiverTypes, callType))
} }
} }
for (declaration in declarations) { declarations.forEach { it.resolveToDescriptorsWithHack().forEach(::processDescriptor) }
if (declaration.getContainingJetFile().isCompiled()) {
//TODO: it's temporary while resolveToDescriptor does not work for compiled declarations
for (descriptor in resolutionFacade.resolveImportReference(moduleDescriptor, declaration.getFqName()!!)) {
if (descriptor is CallableDescriptor && descriptor.getExtensionReceiverParameter() != null) {
processDescriptor(descriptor)
}
}
}
else {
processDescriptor(resolutionFacade.resolveToDescriptor(declaration) as CallableDescriptor)
}
}
return result return result
} }
@@ -172,7 +168,10 @@ public class KotlinIndicesHelper(
public fun getKotlinClasses(nameFilter: (String) -> Boolean, kindFilter: (ClassKind) -> Boolean): Collection<ClassDescriptor> { public fun getKotlinClasses(nameFilter: (String) -> Boolean, kindFilter: (ClassKind) -> Boolean): Collection<ClassDescriptor> {
return KotlinFullClassNameIndex.getInstance().getAllKeys(project).asSequence() return KotlinFullClassNameIndex.getInstance().getAllKeys(project).asSequence()
.map { FqName(it) } .map { FqName(it) }
.filter { nameFilter(it.shortName().asString()) } .filter {
ProgressManager.checkCanceled()
nameFilter(it.shortName().asString())
}
.toList() .toList()
.flatMap { getClassDescriptorsByFQName(it, kindFilter) } .flatMap { getClassDescriptorsByFQName(it, kindFilter) }
} }
@@ -190,6 +189,73 @@ public class KotlinIndicesHelper(
.filter(descriptorFilter) .filter(descriptorFilter)
} }
public fun getObjectMembers(descriptorKindFilter: DescriptorKindFilter, nameFilter: (String) -> Boolean): Collection<CallableDescriptor> {
val result = LinkedHashSet<CallableDescriptor>()
fun addFromIndex(index: StringStubIndexExtension<out KtCallableDeclaration>) {
for (name in index.getAllKeys(project)) {
ProgressManager.checkCanceled()
if (!nameFilter(name)) continue
for (declaration in index.get(name, project, scope)) {
if (declaration.parent.parent !is KtObjectDeclaration) continue
if (!visibilityFilterMayIncludeAccessible && declaration.hasModifier(KtTokens.PRIVATE_KEYWORD)) continue
declaration.resolveToDescriptorsWithHack().filterTo(result) { descriptorKindFilter.accepts(it) && descriptorFilter(it) }
}
}
}
if (descriptorKindFilter.kindMask.and(DescriptorKindFilter.FUNCTIONS_MASK) != 0) {
addFromIndex(KotlinFunctionShortNameIndex.getInstance())
}
if (descriptorKindFilter.kindMask.and(DescriptorKindFilter.VARIABLES_MASK) != 0) {
addFromIndex(KotlinPropertyShortNameIndex.getInstance())
}
return result
}
public fun getJavaStaticMembers(descriptorKindFilter: DescriptorKindFilter, nameFilter: (String) -> Boolean): Collection<CallableDescriptor> {
val result = LinkedHashSet<CallableDescriptor>()
val idFilter = IdFilter.getProjectIdFilter(resolutionFacade.project, false)
val shortNamesCache = PsiShortNamesCache.getInstance(project)
val methodNamesProcessor = Processor<String> { name ->
ProgressManager.checkCanceled()
if (!nameFilter(name)) return@Processor true
for (method in shortNamesCache.getMethodsByName(name, scope)) {
if (!method.hasModifierProperty(PsiModifier.STATIC)) continue
if (!visibilityFilterMayIncludeAccessible && method.hasModifierProperty(PsiModifier.PRIVATE)) continue
val descriptor = method.getJavaMethodDescriptor() ?: continue
if (descriptorKindFilter.accepts(descriptor) && descriptorFilter(descriptor)) {
result.add(descriptor)
}
}
true
}
shortNamesCache.processAllMethodNames(methodNamesProcessor, scope, idFilter)
val fieldNamesProcessor = Processor<String> { name ->
ProgressManager.checkCanceled()
if (!nameFilter(name)) return@Processor true
for (field in shortNamesCache.getFieldsByName(name, scope)) {
if (!field.hasModifierProperty(PsiModifier.STATIC)) continue
if (!visibilityFilterMayIncludeAccessible && field.hasModifierProperty(PsiModifier.PRIVATE)) continue
val descriptor = field.getJavaFieldDescriptor() ?: continue
if (descriptorKindFilter.accepts(descriptor) && descriptorFilter(descriptor)) {
result.add(descriptor)
}
}
true
}
shortNamesCache.processAllFieldNames(fieldNamesProcessor, scope, idFilter)
return result
}
private fun findTopLevelCallables(fqName: FqName): Collection<CallableDescriptor> { private fun findTopLevelCallables(fqName: FqName): Collection<CallableDescriptor> {
return resolutionFacade.resolveImportReference(moduleDescriptor, fqName) return resolutionFacade.resolveImportReference(moduleDescriptor, fqName)
.filterIsInstance<CallableDescriptor>() .filterIsInstance<CallableDescriptor>()
@@ -202,5 +268,14 @@ public class KotlinIndicesHelper(
return CodeInsightSettings.getInstance().EXCLUDED_PACKAGES return CodeInsightSettings.getInstance().EXCLUDED_PACKAGES
.any { excluded -> fqName == excluded || (fqName.startsWith(excluded) && fqName[excluded.length()] == '.') } .any { excluded -> fqName == excluded || (fqName.startsWith(excluded) && fqName[excluded.length()] == '.') }
} }
private fun KtCallableDeclaration.resolveToDescriptorsWithHack(): Collection<CallableDescriptor> {
if (getContainingJetFile().isCompiled()) { //TODO: it's temporary while resolveToDescriptor does not work for compiled declarations
return resolutionFacade.resolveImportReference(moduleDescriptor, fqName!!).filterIsInstance<CallableDescriptor>()
}
else {
return (resolutionFacade.resolveToDescriptor(this) as? CallableDescriptor).singletonOrEmptyList()
}
}
} }
@@ -149,7 +149,7 @@ internal abstract class AutoImportFixBase<T: KtExpression>(expression: T) :
val result = ArrayList<DeclarationDescriptor>() val result = ArrayList<DeclarationDescriptor>()
val indicesHelper = KotlinIndicesHelper(element.getResolutionFacade(), searchScope, ::isVisible, true) val indicesHelper = KotlinIndicesHelper(element.getResolutionFacade(), searchScope, ::isVisible)
val expression = element val expression = element
if (expression is KtSimpleNameExpression) { if (expression is KtSimpleNameExpression) {