Split module idea into idea, idea-core and idea-completion

This commit is contained in:
Valentin Kipyatkov
2015-04-04 22:11:18 +03:00
parent 203c8ea125
commit 412ab8f8b8
150 changed files with 418 additions and 300 deletions
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-runtime" level="project" />
<orderEntry type="library" name="idea-full" level="project" />
<orderEntry type="module" module-name="idea-analysis" />
<orderEntry type="module" module-name="descriptors" />
<orderEntry type="module" module-name="descriptor.loader.java" />
<orderEntry type="module" module-name="light-classes" />
<orderEntry type="module" module-name="frontend" />
<orderEntry type="module" module-name="util" />
<orderEntry type="module" module-name="idea-core" />
<orderEntry type="module" module-name="frontend.java" />
</component>
</module>
@@ -0,0 +1,96 @@
/*
* 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.AllClassesGetter
import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.openapi.roots.ProjectRootModificationTracker
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import org.jetbrains.kotlin.asJava.KotlinLightClass
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper
import org.jetbrains.kotlin.idea.project.ProjectStructureUtil
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
class AllClassesCompletion(val parameters: CompletionParameters,
val lookupElementFactory: LookupElementFactory,
val resolutionFacade: ResolutionFacade,
val bindingContext: BindingContext,
val moduleDescriptor: ModuleDescriptor,
val scope: GlobalSearchScope,
val prefixMatcher: PrefixMatcher,
val kindFilter: (ClassKind) -> Boolean,
val visibilityFilter: (DeclarationDescriptor) -> Boolean) {
fun collect(result: LookupElementsCollector) {
//TODO: this is a temporary hack until we have built-ins in indices
val builtIns = JavaToKotlinClassMap.INSTANCE.allKotlinClasses() + listOf(KotlinBuiltIns.getInstance().getNothing())
val filteredBuiltIns = builtIns.filter { kindFilter(it.getKind()) && prefixMatcher.prefixMatches(it.getName().asString()) }
result.addDescriptorElements(filteredBuiltIns, suppressAutoInsertion = true)
val helper = KotlinIndicesHelper(scope.getProject(), resolutionFacade, bindingContext, scope, moduleDescriptor, visibilityFilter)
result.addDescriptorElements(helper.getClassDescriptors({ prefixMatcher.prefixMatches(it) }, kindFilter),
suppressAutoInsertion = true)
if (!ProjectStructureUtil.isJsKotlinModule(parameters.getOriginalFile() as JetFile)) {
addAdaptedJavaCompletion(result)
}
else {
//TODO: this is a temporary solution for Kotlin/Javascript library until we have virtual file system based on serialized descriptors
addKotlinJavascriptCompletion(result)
}
}
private fun addKotlinJavascriptCompletion(collector: LookupElementsCollector) {
val classDescriptors = allClassDescriptors.getValue().filter { prefixMatcher.prefixMatches(it.getName().asString()) && kindFilter(it.getKind()) }
collector.addDescriptorElements(classDescriptors, suppressAutoInsertion = true)
}
private val allClassDescriptors = CachedValuesManager.getManager(scope.getProject()).createCachedValue( {
val provider = (moduleDescriptor as ModuleDescriptorImpl).getPackageFragmentProvider()
val fragments = DescriptorUtils.getPackagesFqNames(moduleDescriptor).flatMap { provider.getPackageFragments(it) }
val classDescriptors = fragments.flatMap { it.getMemberScope().getAllDescriptors().filter { it is ClassDescriptor} }.map { it as ClassDescriptor }
CachedValueProvider.Result(classDescriptors, ProjectRootModificationTracker.getInstance(scope.getProject()))
}, false)
private fun addAdaptedJavaCompletion(collector: LookupElementsCollector) {
AllClassesGetter.processJavaClasses(parameters, prefixMatcher, true, { psiClass ->
if (psiClass!! !is KotlinLightClass) { // Kotlin class should have already been added as kotlin element before
val kind = when {
psiClass.isAnnotationType() -> ClassKind.ANNOTATION_CLASS
psiClass.isInterface() -> ClassKind.TRAIT
psiClass.isEnum() -> ClassKind.ENUM_CLASS
else -> ClassKind.CLASS
}
if (kindFilter(kind)) {
collector.addElementWithAutoInsertionSuppressed(lookupElementFactory.createLookupElementForJavaClass(psiClass))
}
}
})
}
}
@@ -0,0 +1,443 @@
/*
* 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.*
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.patterns.CharPattern
import com.intellij.patterns.ElementPattern
import com.intellij.patterns.PatternCondition
import com.intellij.patterns.StandardPatterns
import com.intellij.psi.PsiCompiledElement
import com.intellij.psi.PsiElement
import com.intellij.psi.search.DelegatingGlobalSearchScope
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.ProcessingContext
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletion
import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper
import org.jetbrains.kotlin.idea.core.isVisible
import org.jetbrains.kotlin.idea.core.refactoring.comparePossiblyOverridingDescriptors
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
import org.jetbrains.kotlin.idea.util.CallType
import org.jetbrains.kotlin.idea.util.makeNotNullable
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.load.java.descriptors.SamConstructorDescriptorKindExclude
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.prevLeaf
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo
import org.jetbrains.kotlin.resolve.calls.smartcasts.SmartCastUtils
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindExclude
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import kotlin.properties.Delegates
class CompletionSessionConfiguration(
val completeNonImportedDeclarations: Boolean,
val completeNonAccessibleDeclarations: Boolean)
fun CompletionSessionConfiguration(parameters: CompletionParameters) = CompletionSessionConfiguration(
completeNonImportedDeclarations = parameters.getInvocationCount() >= 2,
completeNonAccessibleDeclarations = parameters.getInvocationCount() >= 2)
abstract class CompletionSessionBase(protected val configuration: CompletionSessionConfiguration,
protected val parameters: CompletionParameters,
resultSet: CompletionResultSet) {
protected val position: PsiElement = parameters.getPosition()
private val file = position.getContainingFile() as JetFile
protected val resolutionFacade: ResolutionFacade = file.getResolutionFacade()
protected val moduleDescriptor: ModuleDescriptor = resolutionFacade.findModuleDescriptor(file)
protected val reference: JetSimpleNameReference?
protected val expression: JetExpression?
init {
val reference = position.getParent()?.getReferences()?.firstIsInstanceOrNull<JetSimpleNameReference>()
if (reference != null) {
if (reference.expression is JetLabelReferenceExpression) {
this.expression = reference.expression.getParent().getParent() as? JetExpressionWithLabel
this.reference = null
}
else {
this.expression = reference.expression
this.reference = reference
}
}
else {
this.reference = null
this.expression = null
}
}
protected val bindingContext: BindingContext = resolutionFacade.analyze(position.parents().firstIsInstance<JetElement>(), BodyResolveMode.PARTIAL_FOR_COMPLETION)
protected val inDescriptor: DeclarationDescriptor? = expression?.let { bindingContext.get(BindingContext.RESOLUTION_SCOPE, it)?.getContainingDeclaration() }
private fun singleCharPattern(char: Char): CharPattern {
return StandardPatterns.character().with(
object : PatternCondition<Char>(char.toString()) {
override fun accepts(c: Char, context: ProcessingContext) = c == char
})
}
private val kotlinIdentifierStartPattern: ElementPattern<Char>
private val kotlinIdentifierPartPattern: ElementPattern<Char>
init {
val includeDollar = position.prevLeaf()?.getNode()?.getElementType() != JetTokens.SHORT_TEMPLATE_ENTRY_START
if (includeDollar) {
kotlinIdentifierStartPattern = StandardPatterns.character().javaIdentifierStart()
kotlinIdentifierPartPattern = StandardPatterns.character().javaIdentifierPart()
}
else {
kotlinIdentifierStartPattern = StandardPatterns.and(StandardPatterns.character().javaIdentifierStart(), StandardPatterns.not(singleCharPattern('$')))
kotlinIdentifierPartPattern = StandardPatterns.and(StandardPatterns.character().javaIdentifierPart(), StandardPatterns.not(singleCharPattern('$')))
}
}
protected val prefix: String = CompletionUtil.findIdentifierPrefix(
parameters.getPosition().getContainingFile(),
parameters.getOffset(),
StandardPatterns.or(kotlinIdentifierPartPattern, singleCharPattern('@')),
kotlinIdentifierStartPattern)
protected val resultSet: CompletionResultSet = resultSet
.withPrefixMatcher(prefix)
.addKotlinSorting(parameters)
protected val prefixMatcher: PrefixMatcher = this.resultSet.getPrefixMatcher()
protected val referenceVariantsHelper: ReferenceVariantsHelper = ReferenceVariantsHelper(bindingContext) { isVisibleDescriptor(it) }
protected val lookupElementFactory: LookupElementFactory = run {
val receiverTypes = if (reference != null) {
val expression = reference.expression
val (receivers, callType) = referenceVariantsHelper.getReferenceVariantsReceivers(expression)
val dataFlowInfo = bindingContext.getDataFlowInfo(expression)
var receiverTypes = receivers.flatMap {
SmartCastUtils.getSmartCastVariantsWithLessSpecificExcluded(it, bindingContext, moduleDescriptor, dataFlowInfo)
}
if (callType == CallType.SAFE) {
receiverTypes = receiverTypes.map { it.makeNotNullable() }
}
receiverTypes
}
else {
listOf()
}
LookupElementFactory(receiverTypes)
}
protected val collector: LookupElementsCollector = LookupElementsCollector(
prefixMatcher, parameters, resolutionFacade, lookupElementFactory, inDescriptor, expression?.getParent() is JetSimpleNameStringTemplateEntry)
protected val project: Project = position.getProject()
protected val originalSearchScope: GlobalSearchScope = parameters.getOriginalFile().getResolveScope()
// we need to exclude the original file from scope because our resolve session is built with this file replaced by synthetic one
protected val searchScope: GlobalSearchScope = object : DelegatingGlobalSearchScope(originalSearchScope) {
override fun contains(file: VirtualFile) = super.contains(file) && file != parameters.getOriginalFile().getVirtualFile()
}
protected val indicesHelper: KotlinIndicesHelper
get() = KotlinIndicesHelper(project, resolutionFacade, bindingContext, searchScope, moduleDescriptor) { isVisibleDescriptor(it) }
protected fun isVisibleDescriptor(descriptor: DeclarationDescriptor): Boolean {
if (descriptor is DeclarationDescriptorWithVisibility && inDescriptor != null) {
val visible = descriptor.isVisible(inDescriptor, bindingContext, reference?.expression)
if (visible) return true
if (!configuration.completeNonAccessibleDeclarations) return false
return DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor) !is PsiCompiledElement
}
return true
}
protected fun flushToResultSet() {
collector.flushToResultSet(resultSet)
}
public fun complete(): Boolean {
doComplete()
flushToResultSet()
return !collector.isResultEmpty
}
protected abstract fun doComplete()
// set is used only for completion in code fragments
private var alreadyAddedDescriptors: Collection<DeclarationDescriptor> by Delegates.notNull()
protected fun getReferenceVariants(kindFilter: DescriptorKindFilter, runtimeReceiverType: Boolean): Collection<DeclarationDescriptor> {
val descriptors = referenceVariantsHelper.getReferenceVariants(reference!!.expression, kindFilter, runtimeReceiverType, prefixMatcher.asNameFilter())
if (!runtimeReceiverType) {
if (position.getContainingFile() is JetCodeFragment) {
alreadyAddedDescriptors = descriptors
}
return descriptors
}
else {
return descriptors.filter { desc ->
!alreadyAddedDescriptors.any {
comparePossiblyOverridingDescriptors(project, it, desc)
}
}
}
}
protected fun shouldRunTopLevelCompletion(): Boolean
= configuration.completeNonImportedDeclarations && isNoQualifierContext()
protected fun isNoQualifierContext(): Boolean {
val parent = position.getParent()
return parent is JetSimpleNameExpression && !JetPsiUtil.isSelectorInQualified(parent)
}
protected fun getTopLevelCallables(): Collection<DeclarationDescriptor>
= indicesHelper.getTopLevelCallables({ prefixMatcher.prefixMatches(it) })
protected fun getTopLevelExtensions(): Collection<CallableDescriptor>
= indicesHelper.getCallableTopLevelExtensions({ prefixMatcher.prefixMatches(it) }, reference!!.expression)
protected fun addAllClasses(kindFilter: (ClassKind) -> Boolean) {
AllClassesCompletion(
parameters, lookupElementFactory, resolutionFacade, bindingContext, moduleDescriptor,
searchScope, prefixMatcher, kindFilter, { isVisibleDescriptor(it) }
).collect(collector)
}
}
class BasicCompletionSession(configuration: CompletionSessionConfiguration,
parameters: CompletionParameters,
resultSet: CompletionResultSet)
: CompletionSessionBase(configuration, parameters, resultSet) {
public enum class CompletionKind {
KEYWORDS_ONLY
NAMED_PARAMETERS_ONLY
ALL
TYPES
ANNOTATION_TYPES
ANNOTATION_TYPES_OR_PARAMETER_NAME
}
public val completionKind: CompletionKind = calcCompletionKind()
private fun calcCompletionKind(): CompletionKind {
if (NamedParametersCompletion.isOnlyNamedParameterExpected(position)) {
return CompletionKind.NAMED_PARAMETERS_ONLY
}
if (reference == null) {
return CompletionKind.KEYWORDS_ONLY
}
val annotationEntry = position.getStrictParentOfType<JetAnnotationEntry>()
if (annotationEntry != null) {
val valueArgList = position.getStrictParentOfType<JetValueArgumentList>()
if (valueArgList == null || !annotationEntry.isAncestor(valueArgList)) {
val parent = annotationEntry.getParent()
if (parent is JetDeclarationModifierList && parent.getParent() is JetParameter) {
return CompletionKind.ANNOTATION_TYPES_OR_PARAMETER_NAME
}
return CompletionKind.ANNOTATION_TYPES
}
}
// Check that completion in the type annotation context and if there's a qualified
// expression we are at first of it
val typeReference = position.getStrictParentOfType<JetTypeReference>()
if (typeReference != null) {
val firstPartReference = PsiTreeUtil.findChildOfType(typeReference, javaClass<JetSimpleNameExpression>())
if (firstPartReference == reference.expression) {
return CompletionKind.TYPES
}
}
return CompletionKind.ALL
}
override fun doComplete() {
assert(parameters.getCompletionType() == CompletionType.BASIC)
if (completionKind != CompletionKind.NAMED_PARAMETERS_ONLY) {
val kindFilter = when (completionKind) {
CompletionKind.TYPES ->
DescriptorKindFilter(DescriptorKindFilter.CLASSIFIERS_MASK or DescriptorKindFilter.PACKAGES_MASK) exclude DescriptorKindExclude.EnumEntry
CompletionKind.ANNOTATION_TYPES, CompletionKind.ANNOTATION_TYPES_OR_PARAMETER_NAME ->
DescriptorKindFilter(DescriptorKindFilter.NON_SINGLETON_CLASSIFIERS_MASK or DescriptorKindFilter.PACKAGES_MASK) exclude NonAnnotationClassifierExclude
else ->
DescriptorKindFilter(DescriptorKindFilter.ALL_KINDS_MASK)
}
if (completionKind != CompletionKind.KEYWORDS_ONLY) {
addReferenceVariants(kindFilter, runtimeReceiverType = 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 ->
val keyword = lookupElement.getLookupString()
when (keyword) {
// if "this" is parsed correctly in the current context - insert it and all this@xxx items
"this" -> {
if (expression != null) {
collector.addElements(thisExpressionItems(bindingContext, expression, prefix).map { it.factory() })
}
else {
// for completion in secondary constructor delegation call
collector.addElement(lookupElement)
}
}
// if "return" is parsed correctly in the current context - insert it and all return@xxx items
"return" -> {
if (expression != null) {
collector.addElements(returnExpressionItems(bindingContext, expression))
}
}
"break", "continue" -> {
if (expression != null) {
collector.addElements(breakOrContinueExpressionItems(expression, keyword))
}
}
else -> collector.addElement(lookupElement)
}
}
if (completionKind != CompletionKind.KEYWORDS_ONLY) {
if (!configuration.completeNonImportedDeclarations && isNoQualifierContext()) {
JavaCompletionContributor.advertiseSecondCompletion(project, resultSet)
}
flushToResultSet()
addNonImported(completionKind)
if (position.getContainingFile() is JetCodeFragment) {
flushToResultSet()
addReferenceVariants(kindFilter, runtimeReceiverType = true)
}
}
}
NamedParametersCompletion.complete(position, collector, bindingContext)
}
private object NonAnnotationClassifierExclude : DescriptorKindExclude {
override fun matches(descriptor: DeclarationDescriptor): Boolean {
return if (descriptor is ClassDescriptor)
descriptor.getKind() != ClassKind.ANNOTATION_CLASS
else
descriptor !is ClassifierDescriptor
}
}
private fun addNonImported(completionKind: CompletionKind) {
if (completionKind == CompletionKind.ALL) {
collector.addDescriptorElements(getTopLevelExtensions(), suppressAutoInsertion = true)
}
flushToResultSet()
if (shouldRunTopLevelCompletion()) {
addAllClasses {
if (completionKind != CompletionKind.ANNOTATION_TYPES && completionKind != CompletionKind.ANNOTATION_TYPES_OR_PARAMETER_NAME)
it != ClassKind.ENUM_ENTRY
else
it == ClassKind.ANNOTATION_CLASS
}
if (completionKind == CompletionKind.ALL) {
collector.addDescriptorElements(getTopLevelCallables(), suppressAutoInsertion = true)
}
}
}
private fun addReferenceVariants(kindFilter: DescriptorKindFilter, runtimeReceiverType: Boolean) {
collector.addDescriptorElements(
getReferenceVariants(kindFilter, runtimeReceiverType),
suppressAutoInsertion = false,
withReceiverCast = runtimeReceiverType)
}
}
class SmartCompletionSession(configuration: CompletionSessionConfiguration, parameters: CompletionParameters, resultSet: CompletionResultSet)
: CompletionSessionBase(configuration, parameters, resultSet) {
// we do not include SAM-constructors because they are handled separately and adding them requires iterating of java classes
private val DESCRIPTOR_KIND_MASK = DescriptorKindFilter.VALUES exclude SamConstructorDescriptorKindExclude
override fun doComplete() {
if (expression != null) {
val mapper = ToFromOriginalFileMapper(parameters.getOriginalFile() as JetFile, position.getContainingFile() as JetFile, parameters.getOffset())
val completion = SmartCompletion(expression, resolutionFacade, moduleDescriptor,
bindingContext, { isVisibleDescriptor(it) }, inDescriptor, prefixMatcher, originalSearchScope,
mapper, lookupElementFactory)
val result = completion.execute()
if (result != null) {
collector.addElements(result.additionalItems)
if (reference != null) {
val filter = result.declarationFilter
if (filter != null) {
getReferenceVariants(DESCRIPTOR_KIND_MASK, runtimeReceiverType = false).forEach { collector.addElements(filter(it)) }
flushToResultSet()
processNonImported { collector.addElements(filter(it)) }
flushToResultSet()
if (position.getContainingFile() is JetCodeFragment) {
getReferenceVariants(DESCRIPTOR_KIND_MASK, runtimeReceiverType = true).forEach { collector.addElements(filter(it).map { it.withReceiverCast() }) }
flushToResultSet()
}
}
// it makes no sense to search inheritors if there is no reference because it means that we have prefix like "this@"
result.inheritanceSearcher?.search({ prefixMatcher.prefixMatches(it) }) {
collector.addElement(it)
flushToResultSet()
}
}
}
}
}
private fun processNonImported(processor: (DeclarationDescriptor) -> Unit) {
getTopLevelExtensions().forEach(processor)
flushToResultSet()
if (shouldRunTopLevelCompletion()) {
getTopLevelCallables().forEach(processor)
}
}
}
@@ -0,0 +1,195 @@
/*
* 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.CompletionResultSet
import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.codeInsight.completion.CompletionSorter
import org.jetbrains.kotlin.psi.JetFile
import com.intellij.codeInsight.lookup.LookupElementWeigher
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.WeighingContext
import org.jetbrains.kotlin.idea.completion.*
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.name.isValidJavaFqName
import org.jetbrains.kotlin.resolve.ImportPath
import com.intellij.codeInsight.completion.CompletionType
import org.jetbrains.kotlin.idea.completion.smart.NameSimilarityWeigher
import org.jetbrains.kotlin.idea.completion.smart.SMART_COMPLETION_ITEM_PRIORITY_KEY
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletionItemPriority
import com.intellij.psi.PsiClass
import java.util.HashSet
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
public fun CompletionResultSet.addKotlinSorting(parameters: CompletionParameters): CompletionResultSet {
var sorter = CompletionSorter.defaultSorter(parameters, getPrefixMatcher())!!
sorter = sorter.weighBefore("stats", PriorityWeigher, DeprecatedWeigher, KindWeigher)
if (parameters.getCompletionType() == CompletionType.SMART) {
sorter = sorter.weighBefore("kotlin.kind", NameSimilarityWeigher, SmartCompletionPriorityWeigher)
}
sorter = sorter.weighAfter("stats", JetDeclarationRemotenessWeigher(parameters.getOriginalFile() as JetFile))
sorter = sorter.weighBefore("middleMatching", PreferMatchingItemWeigher)
return withRelevanceSorter(sorter)
}
private object PriorityWeigher : LookupElementWeigher("kotlin.priority") {
override fun weigh(element: LookupElement, context: WeighingContext)
= element.getUserData(ITEM_PRIORITY_KEY) ?: ItemPriority.DEFAULT
}
private object SmartCompletionPriorityWeigher : LookupElementWeigher("kotlin.smartCompletionPriority") {
override fun weigh(element: LookupElement, context: WeighingContext)
= element.getUserData(SMART_COMPLETION_ITEM_PRIORITY_KEY) ?: SmartCompletionItemPriority.DEFAULT
}
private object KindWeigher : LookupElementWeigher("kotlin.kind") {
private enum class Weight {
variable // variable or property
function
keyword
`default`
packages
}
private data class CompoundWeight(val weight: Weight, val callableWeight: CallableWeight? = null) : Comparable<CompoundWeight> {
override fun compareTo(other: CompoundWeight): Int {
if (callableWeight != null && other.callableWeight != null && callableWeight != other.callableWeight) {
return callableWeight.compareTo(other.callableWeight)
}
return weight.compareTo(other.weight)
}
}
override fun weigh(element: LookupElement): CompoundWeight {
val o = element.getObject()
return when (o) {
is DeclarationDescriptorLookupObject -> {
val descriptor = o.descriptor
when (descriptor) {
is VariableDescriptor -> CompoundWeight(Weight.variable, element.getUserData(CALLABLE_WEIGHT_KEY))
is FunctionDescriptor -> CompoundWeight(Weight.function, element.getUserData(CALLABLE_WEIGHT_KEY))
is PackageViewDescriptor -> CompoundWeight(Weight.packages)
else -> CompoundWeight(Weight.default)
}
}
is KeywordLookupObject -> CompoundWeight(Weight.keyword)
else -> CompoundWeight(Weight.default)
}
}
}
private object DeprecatedWeigher : LookupElementWeigher("kotlin.deprecated") {
override fun weigh(element: LookupElement): Int {
val o = element.getObject()
return if (o is DeclarationDescriptorLookupObject && KotlinBuiltIns.isDeprecated(o.descriptor)) 1 else 0
}
}
private object PreferMatchingItemWeigher : LookupElementWeigher("kotlin.preferMatching", false, true) {
override fun weigh(element: LookupElement, context: WeighingContext): Comparable<Int> {
val prefix = context.itemPattern(element)
return if (element.getLookupString() == prefix) 0 else 1
}
}
private class JetDeclarationRemotenessWeigher(private val file: JetFile) : LookupElementWeigher("kotlin.declarationRemoteness") {
private val importCache = ImportCache()
private enum class Weight {
kotlinDefaultImport
thisFile
preciseImport
allUnderImport
`default`
hasImportFromSamePackage
notImported
notToBeUsedInKotlin
}
override fun weigh(element: LookupElement): Weight {
val o = element.getObject()
if (o is DeclarationDescriptorLookupObject) {
val elementFile = o.psiElement?.getContainingFile()
if (elementFile is JetFile && elementFile.getOriginalFile() == file) {
return Weight.thisFile
}
}
val qualifiedName = qualifiedName(o)
// Invalid name can be met for companion object descriptor: Test.MyTest.A.<no name provided>.testOther
if (qualifiedName != null && isValidJavaFqName(qualifiedName)) {
val importPath = ImportPath(qualifiedName)
val fqName = importPath.fqnPart()
return when {
JavaToKotlinClassMap.INSTANCE.mapPlatformClass(fqName).isNotEmpty() -> Weight.notToBeUsedInKotlin
ImportInsertHelper.getInstance(file.getProject()).isImportedWithDefault(importPath, file) -> Weight.kotlinDefaultImport
importCache.isImportedWithPreciseImport(fqName) -> Weight.preciseImport
importCache.isImportedWithAllUnderImport(fqName) -> Weight.allUnderImport
importCache.hasPreciseImportFromPackage(fqName.parent()) -> Weight.hasImportFromSamePackage
else -> Weight.notImported
}
}
return Weight.default
}
private fun qualifiedName(lookupObject: Any): String? {
return when (lookupObject) {
is DeclarationDescriptorLookupObject -> DescriptorUtils.getFqName(lookupObject.descriptor).toString()
is PsiClass -> lookupObject.getQualifiedName()
else -> null
}
}
private inner class ImportCache {
private val preciseImports = HashSet<FqName>()
private val preciseImportPackages = HashSet<FqName>()
private val allUnderImports = HashSet<FqName>()
init {
for (import in file.getImportDirectives()) {
val importPath = import.getImportPath() ?: continue
val fqName = importPath.fqnPart()
if (importPath.isAllUnder()) {
allUnderImports.add(fqName)
}
else {
preciseImports.add(fqName)
preciseImportPackages.add(fqName.parent())
}
}
}
fun isImportedWithPreciseImport(name: FqName) = name in preciseImports
fun isImportedWithAllUnderImport(name: FqName) = name.parent() in allUnderImports
fun hasPreciseImportFromPackage(packageName: FqName) = packageName in preciseImportPackages
}
}
@@ -0,0 +1,353 @@
/*
* 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.*
import com.intellij.codeInsight.lookup.*
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.util.Key
import com.intellij.util.PlatformIcons
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
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.FuzzyType
import org.jetbrains.kotlin.idea.util.getImplicitReceiversWithInstance
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils
import java.util.ArrayList
enum class ItemPriority {
MULTIPLE_ARGUMENTS_ITEM
DEFAULT
BACKING_FIELD
NAMED_PARAMETER
}
val ITEM_PRIORITY_KEY = Key<ItemPriority>("ITEM_PRIORITY_KEY")
fun LookupElement.assignPriority(priority: ItemPriority): LookupElement {
putUserData(ITEM_PRIORITY_KEY, priority)
return this
}
fun LookupElement.suppressAutoInsertion() = AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(this)
fun LookupElement.withReceiverCast(): LookupElement {
return object: LookupElementDecorator<LookupElement>(this) {
override fun handleInsert(context: InsertionContext) {
super.handleInsert(context)
CastReceiverInsertHandler.handleInsert(context, getDelegate())
}
}
}
fun LookupElement.withBracesSurrounding(): LookupElement {
return object: LookupElementDecorator<LookupElement>(this) {
override fun handleInsert(context: InsertionContext) {
val startOffset = context.getStartOffset()
context.getDocument().insertString(startOffset, "{")
context.getOffsetMap().addOffset(CompletionInitializationContext.START_OFFSET, startOffset + 1)
val tailOffset = context.getTailOffset()
context.getDocument().insertString(tailOffset, "}")
context.setTailOffset(tailOffset)
super.handleInsert(context)
}
}
}
val KEEP_OLD_ARGUMENT_LIST_ON_TAB_KEY = Key<Unit>("KEEP_OLD_ARGUMENT_LIST_ON_TAB_KEY")
fun LookupElement.keepOldArgumentListOnTab(): LookupElement {
putUserData(KEEP_OLD_ARGUMENT_LIST_ON_TAB_KEY, Unit)
return this
}
fun rethrowWithCancelIndicator(exception: ProcessCanceledException): ProcessCanceledException {
val indicator = CompletionService.getCompletionService().getCurrentCompletion() as CompletionProgressIndicator
// Force cancel to avoid deadlock in CompletionThreading.delegateWeighing()
if (!indicator.isCanceled()) {
indicator.cancel()
}
return exception
}
fun PrefixMatcher.asNameFilter() = { (name: Name) ->
if (name.isSpecial()) {
false
}
else {
val identifier = name.getIdentifier()
if (getPrefix().startsWith("$")) { // we need properties from scope for backing field completion
prefixMatches("$" + identifier)
}
else {
prefixMatches(identifier)
}
}
}
fun LookupElementPresentation.prependTailText(text: String, grayed: Boolean) {
val tails = getTailFragments()
clearTail()
appendTailText(text, grayed)
tails.forEach { appendTailText(it.text, it.isGrayed()) }
}
enum class CallableWeight {
local // local non-extension
thisClassMember
baseClassMember
thisTypeExtension
baseTypeExtension
global // global non-extension
notApplicableReceiverNullable
}
val CALLABLE_WEIGHT_KEY = Key<CallableWeight>("CALLABLE_WEIGHT_KEY")
fun descriptorsEqualWithSubstitution(descriptor1: DeclarationDescriptor?, descriptor2: DeclarationDescriptor?): Boolean {
if (descriptor1 == descriptor2) return true
if (descriptor1 == null || descriptor2 == null) return false
if (descriptor1.getOriginal() != descriptor2.getOriginal()) return false
if (descriptor1 !is CallableDescriptor) return true
descriptor2 as CallableDescriptor
// optimization:
if (descriptor1 == descriptor1.getOriginal() && descriptor2 == descriptor2.getOriginal()) return true
if (descriptor1.getReturnType() != descriptor2.getReturnType()) return false
val parameters1 = descriptor1.getValueParameters()
val parameters2 = descriptor2.getValueParameters()
if (parameters1.size() != parameters2.size()) return false
for (i in parameters1.indices) {
if (parameters1[i].getType() != parameters2[i].getType()) return false
}
return true
}
fun InsertionContext.isAfterDot(): Boolean {
var offset = getStartOffset()
val chars = getDocument().getCharsSequence()
while (offset > 0) {
offset--
val c = chars.charAt(offset)
if (!Character.isWhitespace(c)) {
return c == '.'
}
}
return false
}
// do not complete this items by prefix like "is"
fun shouldCompleteThisItems(prefixMatcher: PrefixMatcher): Boolean {
val prefix = prefixMatcher.getPrefix()
val s = "this@"
return prefix.startsWith(s) || s.startsWith(prefix)
}
data class ThisItemInfo(val factory: () -> LookupElement, val type: FuzzyType)
fun thisExpressionItems(bindingContext: BindingContext, position: JetExpression, prefix: String): Collection<ThisItemInfo> {
val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, position] ?: return listOf()
val result = ArrayList<ThisItemInfo>()
for ((i, receiver) in scope.getImplicitReceiversWithInstance().withIndex()) {
val thisType = receiver.getType()
val fuzzyType = FuzzyType(thisType, listOf())
fun createLookupElement(label: String?): LookupElement {
var element = createKeywordWithLabelElement("this", label)
element = element.withTypeText(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(thisType))
return element
}
if (i == 0) {
result.add(ThisItemInfo({ createLookupElement(null) }, fuzzyType))
if (!prefix.startsWith("this@")) continue // if prefix does not start with "this@" do not include immediate this in the form with label
}
val label = thisQualifierName(receiver) ?: continue
result.add(ThisItemInfo({ createLookupElement(label) }, fuzzyType))
}
return result
}
private fun thisQualifierName(receiver: ReceiverParameterDescriptor): String? {
val descriptor = receiver.getContainingDeclaration()
val name = descriptor.getName()
if (!name.isSpecial()) {
return name.asString()
}
val functionLiteral = DescriptorToSourceUtils.descriptorToDeclaration(descriptor) as? JetFunctionLiteral ?: return null
return functionLiteralLabel(functionLiteral)
}
private fun functionLiteralLabel(functionLiteral: JetFunctionLiteral): String?
= functionLiteralLabelAndCall(functionLiteral).first
private fun functionLiteralLabelAndCall(functionLiteral: JetFunctionLiteral): Pair<String?, JetCallExpression?> {
val literalParent = (functionLiteral.getParent() as JetFunctionLiteralExpression).getParent()
fun JetValueArgument.callExpression(): JetCallExpression? {
val parent = getParent()
return (if (parent is JetValueArgumentList) parent else this).getParent() as? JetCallExpression
}
when (literalParent) {
is JetLabeledExpression -> {
val callExpression = (literalParent.getParent() as? JetValueArgument)?.callExpression()
return Pair(literalParent.getLabelName(), callExpression)
}
is JetValueArgument -> {
val callExpression = literalParent.callExpression()
val label = (callExpression?.getCalleeExpression() as? JetSimpleNameExpression)?.getReferencedName()
return Pair(label, callExpression)
}
else -> {
return Pair(null, null)
}
}
}
fun returnExpressionItems(bindingContext: BindingContext, position: JetElement): Collection<LookupElement> {
val result = ArrayList<LookupElement>()
for (parent in position.parents()) {
if (parent is JetDeclarationWithBody) {
val returnsUnit = returnsUnit(parent, bindingContext)
if (parent is JetFunctionLiteral) {
val (label, call) = functionLiteralLabelAndCall(parent)
if (label != null) {
result.add(createKeywordWithLabelElement("return", label, addSpace = !returnsUnit))
}
// check if the current function literal is inlined and stop processing outer declarations if it's not
val callee = call?.getCalleeExpression() as? JetReferenceExpression ?: break // not inlined
val target = bindingContext[BindingContext.REFERENCE_TARGET, callee] as? SimpleFunctionDescriptor ?: break // not inlined
if (!target.getInlineStrategy().isInline()) break // not inlined
}
else {
if (parent.hasBlockBody()) {
result.add(createKeywordWithLabelElement("return", null, addSpace = !returnsUnit))
}
break
}
}
}
return result
}
private fun returnsUnit(declaration: JetDeclarationWithBody, bindingContext: BindingContext): Boolean {
val callable = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration] as? CallableDescriptor ?: return true
val returnType = callable.getReturnType() ?: return true
return KotlinBuiltIns.isUnit(returnType)
}
private fun createKeywordWithLabelElement(keyword: String, label: String?, addSpace: Boolean): LookupElement {
val element = createKeywordWithLabelElement(keyword, label)
return if (addSpace) {
object: LookupElementDecorator<LookupElement>(element) {
override fun handleInsert(context: InsertionContext) {
WithTailInsertHandler.spaceTail().handleInsert(context, getDelegate())
}
}
}
else {
element
}
}
private fun createKeywordWithLabelElement(keyword: String, label: String?): LookupElementBuilder {
var element = LookupElementBuilder.create(KeywordLookupObject, if (label == null) keyword else "$keyword@$label")
element = element.withPresentableText(keyword)
element = element.withBoldness(true)
if (label != null) {
element = element.withTailText("@$label", false)
}
return element
}
fun breakOrContinueExpressionItems(position: JetElement, breakOrContinue: String): Collection<LookupElement> {
val result = ArrayList<LookupElement>()
for (parent in position.parents()) {
when (parent) {
is JetLoopExpression -> {
if (result.isEmpty()) {
result.add(createKeywordWithLabelElement(breakOrContinue, null))
}
val label = (parent.getParent() as? JetLabeledExpression)?.getLabelName()
if (label != null) {
result.add(createKeywordWithLabelElement(breakOrContinue, label))
}
}
is JetDeclarationWithBody -> break //TODO: support non-local break's&continue's when they are supported by compiler
}
}
return result
}
fun LookupElementFactory.createBackingFieldLookupElement(
property: PropertyDescriptor,
inDescriptor: DeclarationDescriptor?,
resolutionFacade: ResolutionFacade
): LookupElement? {
if (inDescriptor == null) return null // no backing field accessible
val insideAccessor = inDescriptor is PropertyAccessorDescriptor && inDescriptor.getCorrespondingProperty() == property
if (!insideAccessor) {
val container = property.getContainingDeclaration()
if (container !is ClassDescriptor || !DescriptorUtils.isAncestor(container, inDescriptor, false)) return null // backing field not accessible
}
val declaration = (DescriptorToSourceUtils.descriptorToDeclaration(property) as? JetProperty) ?: return null
val accessors = declaration.getAccessors()
if (accessors.all { it.getBodyExpression() == null }) return null // makes no sense to access backing field - it's the same as accessing property directly
val bindingContext = resolutionFacade.analyze(declaration)
if (!bindingContext[BindingContext.BACKING_FIELD_REQUIRED, property]) return null
val lookupElement = createLookupElement(resolutionFacade, property, true)
return object : LookupElementDecorator<LookupElement>(lookupElement) {
override fun getLookupString() = "$" + super.getLookupString()
override fun getAllLookupStrings() = setOf(getLookupString())
override fun renderElement(presentation: LookupElementPresentation) {
super.renderElement(presentation)
presentation.setItemText("$" + presentation.getItemText())
presentation.setIcon(PlatformIcons.FIELD_ICON) //TODO: special icon
}
}.assignPriority(ItemPriority.BACKING_FIELD)
}
@@ -0,0 +1,40 @@
/*
* 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 org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue
import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver
import org.jetbrains.kotlin.psi.JetExpression
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
fun renderDataFlowValue(value: DataFlowValue): String? {
// If it is not a stable identifier, there's no point in rendering it
if (!value.isStableIdentifier()) return null
fun renderId(id: Any?): String? {
return when (id) {
is JetExpression -> id.getText()
is ThisReceiver -> "this@${id.getDeclarationDescriptor().getName()}"
is VariableDescriptor -> id.getName().asString()
is PackageViewDescriptor -> id.getFqName().asString()
is com.intellij.openapi.util.Pair<*, *> -> renderId(id.first) + "." + renderId(id.second)
else -> null
}
}
return renderId(value.getId())
}
@@ -0,0 +1,58 @@
/*
* 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.openapi.diagnostic.Logger
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
/**
* Stores information about resolved descriptor and position of that descriptor.
* Position will be used for sorting
*/
public class DeclarationDescriptorLookupObject(
public val descriptor: DeclarationDescriptor,
private val resolutionFacade: ResolutionFacade,
public val psiElement: PsiElement?
) {
override fun toString(): String {
return super.toString() + " " + descriptor
}
override fun hashCode(): Int {
return descriptor.getOriginal().hashCode()
}
override fun equals(other: Any?): Boolean {
if (this identityEquals other) return true
if (other == null || javaClass != other.javaClass) return false
val lookupObject = other as DeclarationDescriptorLookupObject
if (resolutionFacade != lookupObject.resolutionFacade) {
LOG.warn("Descriptors from different resolve sessions")
return false
}
return descriptorsEqualWithSubstitution(descriptor, lookupObject.descriptor)
}
companion object {
private val LOG = Logger.getInstance("#" + javaClass<DeclarationDescriptorLookupObject>().getName())
}
}
@@ -0,0 +1,362 @@
/*
* 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 org.jetbrains.kotlin.psi.JetExpression
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.psi.JetValueArgument
import org.jetbrains.kotlin.psi.JetValueArgumentList
import org.jetbrains.kotlin.psi.JetCallExpression
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import com.intellij.lang.ASTNode
import org.jetbrains.kotlin.psi.JetQualifiedExpression
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.calls.util.CallMaker
import org.jetbrains.kotlin.resolve.calls.context.ContextDependency
import org.jetbrains.kotlin.resolve.calls.context.CheckValueArgumentsMode
import org.jetbrains.kotlin.resolve.calls.checkers.CompositeChecker
import org.jetbrains.kotlin.di.InjectorForMacros
import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import java.util.HashSet
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.psi.JetBinaryExpression
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.JetIfExpression
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.psi.JetContainerNode
import org.jetbrains.kotlin.resolve.calls.callUtil.noErrorsInValueArguments
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.psi.JetBlockExpression
import org.jetbrains.kotlin.idea.util.makeNotNullable
import org.jetbrains.kotlin.psi.JetWhenConditionWithExpression
import org.jetbrains.kotlin.psi.JetWhenEntry
import org.jetbrains.kotlin.psi.JetWhenExpression
import org.jetbrains.kotlin.psi.JetCallElement
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
import org.jetbrains.kotlin.resolve.DelegatingBindingTrace
import org.jetbrains.kotlin.psi.JetPrefixExpression
import org.jetbrains.kotlin.resolve.calls.util.DelegatingCall
import org.jetbrains.kotlin.psi.JetFunctionLiteralArgument
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
import org.jetbrains.kotlin.psi.JetArrayAccessExpression
import org.jetbrains.kotlin.psi.JetProperty
import org.jetbrains.kotlin.psi.JetDeclarationWithBody
import org.jetbrains.kotlin.psi.JetReturnExpression
import org.jetbrains.kotlin.resolve.bindingContextUtil.getTargetFunctionDescriptor
import org.jetbrains.kotlin.idea.completion.smart.toList
import org.jetbrains.kotlin.descriptors.PropertyGetterDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.resolve.calls.results.ResolutionStatus
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils
import org.jetbrains.kotlin.resolve.calls.checkers.AdditionalTypeChecker
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
enum class Tail {
COMMA
RPARENTH
ELSE
}
open data class ExpectedInfo(val type: JetType, val name: String?, val tail: Tail?)
class PositionalArgumentExpectedInfo(type: JetType, name: String?, tail: Tail?, val function: FunctionDescriptor, val argumentIndex: Int)
: ExpectedInfo(type, name, tail) {
override fun equals(other: Any?)
= other is PositionalArgumentExpectedInfo && super.equals(other) && function == other.function && argumentIndex == other.argumentIndex
override fun hashCode()
= function.hashCode()
}
class ExpectedInfos(
val bindingContext: BindingContext,
val resolutionFacade: ResolutionFacade,
val moduleDescriptor: ModuleDescriptor,
val useHeuristicSignatures: Boolean
) {
public fun calculate(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
return calculateForArgument(expressionWithType)
?: calculateForFunctionLiteralArgument(expressionWithType)
?: calculateForEqAndAssignment(expressionWithType)
?: calculateForIf(expressionWithType)
?: calculateForElvis(expressionWithType)
?: calculateForBlockExpression(expressionWithType)
?: calculateForWhenEntryValue(expressionWithType)
?: calculateForExclOperand(expressionWithType)
?: calculateForInitializer(expressionWithType)
?: calculateForExpressionBody(expressionWithType)
?: calculateForReturn(expressionWithType)
?: getFromBindingContext(expressionWithType)
}
private fun calculateForArgument(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
val argument = expressionWithType.getParent() as? JetValueArgument ?: return null
if (argument.isNamed()) return null //TODO - support named arguments (also do not forget to check for presence of named arguments before)
val argumentList = argument.getParent() as? JetValueArgumentList ?: return null
val argumentIndex = argumentList.getArguments().indexOf(argument)
val callElement = argumentList.getParent() as? JetCallElement ?: return null
return calculateForArgument(callElement, argumentIndex, false)
}
private fun calculateForFunctionLiteralArgument(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
val functionLiteralArgument = expressionWithType.getParent() as? JetFunctionLiteralArgument
val callExpression = functionLiteralArgument?.getParent() as? JetCallExpression
if (callExpression != null) {
if (callExpression.getFunctionLiteralArguments().firstOrNull()?.getArgumentExpression() == expressionWithType) {
return calculateForArgument(callExpression, callExpression.getValueArguments().size() - 1, true)
}
}
return null
}
private fun calculateForArgument(callElement: JetCallElement, argumentIndex: Int, isFunctionLiteralArgument: Boolean): Collection<ExpectedInfo>? {
val calleeExpression = callElement.getCalleeExpression()
val parent = callElement.getParent()
val receiver: ReceiverValue
val callOperationNode: ASTNode?
if (parent is JetQualifiedExpression && callElement == parent.getSelectorExpression()) {
val receiverExpression = parent.getReceiverExpression()
val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE, receiverExpression]
val qualifier = bindingContext[BindingContext.QUALIFIER, receiverExpression]
if (expressionType != null) {
receiver = ExpressionReceiver(receiverExpression, expressionType)
callOperationNode = parent.getOperationTokenNode()
}
else if (qualifier != null) {
receiver = qualifier
callOperationNode = null
}
else {
return null
}
}
else {
receiver = ReceiverValue.NO_RECEIVER
callOperationNode = null
}
var call = CallMaker.makeCall(receiver, callOperationNode, callElement)
if (!isFunctionLiteralArgument) { // leave only arguments before the current one
call = object : DelegatingCall(call) {
override fun getValueArguments() = super.getValueArguments().subList(0, argumentIndex)
override fun getValueArgumentList() = null
}
}
val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, calleeExpression] ?: return null //TODO: discuss it
val expectedType = (callElement as? JetExpression)?.let { bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, it] } ?: TypeUtils.NO_EXPECTED_TYPE
val dataFlowInfo = bindingContext.getDataFlowInfo(calleeExpression)
val callResolutionContext = BasicCallResolutionContext.create(
DelegatingBindingTrace(bindingContext, "Temporary trace for completion"),
resolutionScope,
call,
expectedType,
dataFlowInfo,
ContextDependency.INDEPENDENT,
CheckValueArgumentsMode.ENABLED,
CompositeChecker(listOf()),
AdditionalTypeChecker.Composite(listOf()),
false).replaceCollectAllCandidates(true)
val callResolver = InjectorForMacros(
callElement.getProject(),
resolutionFacade.findModuleDescriptor(callElement)
).getCallResolver()
val results: OverloadResolutionResults<FunctionDescriptor> = callResolver.resolveFunctionCall(callResolutionContext)
val expectedInfos = HashSet<ExpectedInfo>()
for (candidate: ResolvedCall<FunctionDescriptor> in results.getAllCandidates()!!) {
val status = candidate.getStatus()
if (status == ResolutionStatus.RECEIVER_TYPE_ERROR || status == ResolutionStatus.RECEIVER_PRESENCE_ERROR) continue
// consider only candidates with more arguments than in the truncated call and with all arguments before the current one matched
if (candidate.noErrorsInValueArguments() && (candidate.getCandidateDescriptor().getValueParameters().size() > argumentIndex || isFunctionLiteralArgument)) {
val descriptor = candidate.getResultingDescriptor()
val thisReceiver = ExpressionTypingUtils.normalizeReceiverValueForVisibility(candidate.getDispatchReceiver(), bindingContext)
if (!Visibilities.isVisible(thisReceiver, descriptor, resolutionScope.getContainingDeclaration())) continue
val parameters = descriptor.getValueParameters()
if (isFunctionLiteralArgument && argumentIndex != parameters.lastIndex) continue
val tail = if (isFunctionLiteralArgument)
null
else if (argumentIndex == parameters.lastIndex)
Tail.RPARENTH //TODO: support square brackets
else if (parameters.drop(argumentIndex + 1).all { it.hasDefaultValue() || it.getVarargElementType() != null })
null
else
Tail.COMMA
val parameter = parameters[argumentIndex]
val expectedName = if (descriptor.hasSynthesizedParameterNames()) null else parameter.getName().asString()
val parameterType = if (useHeuristicSignatures)
HeuristicSignatures.correctedParameterType(descriptor, argumentIndex, moduleDescriptor, callElement.getProject()) ?: parameter.getType()
else
parameter.getType()
expectedInfos.add(PositionalArgumentExpectedInfo(parameterType, expectedName, tail, descriptor, argumentIndex))
}
}
return expectedInfos
}
private fun calculateForEqAndAssignment(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
val binaryExpression = expressionWithType.getParent() as? JetBinaryExpression
if (binaryExpression != null) {
val operationToken = binaryExpression.getOperationToken()
if (operationToken == JetTokens.EQ || operationToken == JetTokens.EQEQ || operationToken == JetTokens.EXCLEQ
|| operationToken == JetTokens.EQEQEQ || operationToken == JetTokens.EXCLEQEQEQ) {
val otherOperand = if (expressionWithType == binaryExpression.getRight()) binaryExpression.getLeft() else binaryExpression.getRight()
if (otherOperand != null) {
val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE, otherOperand] ?: return null
return listOf(ExpectedInfo(expressionType, expectedNameFromExpression(otherOperand), null))
}
}
}
return null
}
private fun calculateForIf(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
val ifExpression = (expressionWithType.getParent() as? JetContainerNode)?.getParent() as? JetIfExpression ?: return null
return when (expressionWithType) {
ifExpression.getCondition() -> listOf(ExpectedInfo(KotlinBuiltIns.getInstance().getBooleanType(), null, Tail.RPARENTH))
ifExpression.getThen() -> calculate(ifExpression)?.map { ExpectedInfo(it.type, it.name, Tail.ELSE) }
ifExpression.getElse() -> {
val ifExpectedInfo = calculate(ifExpression)
val thenType = bindingContext[BindingContext.EXPRESSION_TYPE, ifExpression.getThen()]
if (thenType != null)
ifExpectedInfo?.filter { it.type.isSubtypeOf(thenType) }
else
ifExpectedInfo
}
else -> return null
}
}
private fun calculateForElvis(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
val binaryExpression = expressionWithType.getParent() as? JetBinaryExpression
if (binaryExpression != null) {
val operationToken = binaryExpression.getOperationToken()
if (operationToken == JetTokens.ELVIS && expressionWithType == binaryExpression.getRight()) {
val leftExpression = binaryExpression.getLeft() ?: return null
val leftType = bindingContext[BindingContext.EXPRESSION_TYPE, leftExpression]
val leftTypeNotNullable = leftType?.makeNotNullable()
val expectedInfos = calculate(binaryExpression)
if (expectedInfos != null) {
return if (leftTypeNotNullable != null)
expectedInfos.filter { leftTypeNotNullable.isSubtypeOf(it.type) }
else
expectedInfos
}
else if (leftTypeNotNullable != null) {
return listOf(ExpectedInfo(leftTypeNotNullable, null, null))
}
}
}
return null
}
private fun calculateForBlockExpression(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
val block = expressionWithType.getParent() as? JetBlockExpression ?: return null
if (expressionWithType != block.getStatements().last()) return null
return calculate(block)?.map { ExpectedInfo(it.type, it.name, null) }
}
private fun calculateForWhenEntryValue(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
val condition = expressionWithType.getParent() as? JetWhenConditionWithExpression ?: return null
val entry = condition.getParent() as JetWhenEntry
val whenExpression = entry.getParent() as JetWhenExpression
val subject = whenExpression.getSubjectExpression()
if (subject != null) {
val subjectType = bindingContext[BindingContext.EXPRESSION_TYPE, subject] ?: return null
return listOf(ExpectedInfo(subjectType, null, null))
}
else {
return listOf(ExpectedInfo(KotlinBuiltIns.getInstance().getBooleanType(), null, null))
}
}
private fun calculateForExclOperand(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
val prefixExpression = expressionWithType.getParent() as? JetPrefixExpression ?: return null
if (prefixExpression.getOperationToken() != JetTokens.EXCL) return null
return listOf(ExpectedInfo(KotlinBuiltIns.getInstance().getBooleanType(), null, null))
}
private fun calculateForInitializer(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
val property = expressionWithType.getParent() as? JetProperty ?: return null
if (expressionWithType != property.getInitializer()) return null
val propertyDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, property] as? VariableDescriptor ?: return null
return listOf(ExpectedInfo(propertyDescriptor.getType(), propertyDescriptor.getName().asString(), null))
}
private fun calculateForExpressionBody(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
val declaration = expressionWithType.getParent() as? JetDeclarationWithBody ?: return null
if (expressionWithType != declaration.getBodyExpression() || declaration.hasBlockBody()) return null
val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration] as? FunctionDescriptor ?: return null
return functionReturnValueExpectedInfo(descriptor).toList()
}
private fun calculateForReturn(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
val returnExpression = expressionWithType.getParent() as? JetReturnExpression ?: return null
val descriptor = returnExpression.getTargetFunctionDescriptor(bindingContext) ?: return null
return functionReturnValueExpectedInfo(descriptor).toList()
}
private fun functionReturnValueExpectedInfo(descriptor: FunctionDescriptor): ExpectedInfo? {
return when (descriptor) {
is SimpleFunctionDescriptor -> ExpectedInfo(descriptor.getReturnType() ?: return null, descriptor.getName().asString(), null)
is PropertyGetterDescriptor -> {
if (descriptor !is PropertyGetterDescriptor) return null
val property = descriptor.getCorrespondingProperty()
ExpectedInfo(property.getType(), property.getName().asString(), null)
}
else -> null
}
}
private fun getFromBindingContext(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
val expectedType = bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, expressionWithType] ?: return null
return listOf(ExpectedInfo(expectedType, null, null))
}
private fun expectedNameFromExpression(expression: JetExpression?): String? {
return when (expression) {
is JetSimpleNameExpression -> expression.getReferencedName()
is JetQualifiedExpression -> expectedNameFromExpression(expression.getSelectorExpression())
is JetCallExpression -> expectedNameFromExpression(expression.getCalleeExpression())
is JetArrayAccessExpression -> expectedNameFromExpression(expression.getArrayExpression())?.fromPlural()
else -> null
}
}
private fun String.fromPlural()
= if (endsWith("s")) substring(0, length() - 1) else this
}
@@ -0,0 +1,107 @@
/*
* 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 org.jetbrains.kotlin.descriptors.FunctionDescriptor
import java.util.HashMap
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.types.SubstitutionUtils
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.di.InjectorForMacros
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.resolve.JetModuleUtil
import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.resolve.BindingTraceContext
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.resolve.scopes.JetScope
import org.jetbrains.kotlin.resolve.scopes.ChainedScope
import org.jetbrains.kotlin.types.TypeUtils
public object HeuristicSignatures {
private val signatures = HashMap<Pair<FqName, Name>, List<String>>()
init {
registerSignature("kotlin.Collection", "contains", "E")
registerSignature("kotlin.Collection", "containsAll", "kotlin.Collection<E>")
registerSignature("kotlin.MutableCollection", "remove", "E")
registerSignature("kotlin.MutableCollection", "removeAll", "kotlin.Collection<E>")
registerSignature("kotlin.MutableCollection", "retainAll", "kotlin.Collection<E>")
registerSignature("kotlin.List", "indexOf", "E")
registerSignature("kotlin.List", "lastIndexOf", "E")
registerSignature("kotlin.Map", "get", "K")
registerSignature("kotlin.Map", "containsKey", "K")
registerSignature("kotlin.Map", "containsValue", "V")
registerSignature("kotlin.MutableMap", "remove", "K")
}
private fun registerSignature(
classFqName: String,
name: String,
vararg parameterTypes: String) {
signatures[FqName(classFqName) to Name.identifier(name)] = parameterTypes.toList()
}
public fun correctedParameterType(function: FunctionDescriptor, parameterIndex: Int, moduleDescriptor: ModuleDescriptor, project: Project): JetType? {
val ownerType = function.getDispatchReceiverParameter()?.getType() ?: return null
val superFunctions = function.getOverriddenDescriptors()
if (superFunctions.isNotEmpty()) {
for (superFunction in superFunctions) {
val correctedType = correctedParameterType(superFunction, parameterIndex, moduleDescriptor, project) ?: continue
val typeSubstitutor = SubstitutionUtils.buildDeepSubstitutor(ownerType)
return typeSubstitutor.safeSubstitute(correctedType, Variance.INVARIANT)
}
return null
}
else {
val ownerClass = ownerType.getConstructor().getDeclarationDescriptor() ?: return null
val classFqName = DescriptorUtils.getFqNameSafe(ownerClass)
val parameterTypes = signatures[classFqName to function.getName()] ?: return null
val typeStr = parameterTypes[parameterIndex]
val typeParameters = ownerClass.getTypeConstructor().getParameters()
val type = typeFromText(typeStr, typeParameters, moduleDescriptor, project)
// now substitute type parameters with actual arguments
val typeArgs = ownerType.getArguments()
val typeArgsMap = typeParameters.indices.map { typeParameters[it] to typeArgs[it] }.toMap()
val substitutor = TypeUtils.makeSubstitutorForTypeParametersMap(typeArgsMap)
return substitutor.substitute(type, Variance.INVARIANT)
}
}
private fun typeFromText(text: String, typeParameters: Collection<TypeParameterDescriptor>, moduleDescriptor: ModuleDescriptor, project: Project): JetType {
val typeRef = JetPsiFactory(project).createType(text)
val injector = InjectorForMacros(project, moduleDescriptor)
val rootPackagesScope = JetModuleUtil.getSubpackagesOfRootScope(moduleDescriptor)
val typeParametersScope = TypeParametersScope(typeParameters)
val scope = ChainedScope(moduleDescriptor, "Root packages + type parameters", typeParametersScope, rootPackagesScope)
val type = injector.getTypeResolver().resolveType(scope, typeRef, BindingTraceContext(), false)
assert(!type.isError()) { "No type resolved from '$text'" }
return type
}
private class TypeParametersScope(params: Collection<TypeParameterDescriptor>) : JetScope by JetScope.Empty {
private val paramsByName = params.map { it.getName() to it }.toMap()
override fun getClassifier(name: Name) = paramsByName[name]
}
}
@@ -0,0 +1,117 @@
/*
* 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.*
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.patterns.PlatformPatterns.psiElement
import com.intellij.patterns.StandardPatterns
import com.intellij.util.ProcessingContext
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.KotlinCacheService
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.completion.KotlinCompletionContributor
import org.jetbrains.kotlin.idea.completion.LookupElementFactory
import org.jetbrains.kotlin.idea.kdoc.getParamDescriptors
import org.jetbrains.kotlin.idea.kdoc.getResolutionScope
import org.jetbrains.kotlin.kdoc.lexer.KDocTokens
import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink
import org.jetbrains.kotlin.kdoc.psi.impl.KDocName
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.KotlinCodeAnalyzer
class KDocCompletionContributor(): CompletionContributor() {
init {
extend(CompletionType.BASIC, psiElement().inside(javaClass<KDocName>()),
KDocNameCompletionProvider)
extend(CompletionType.BASIC,
psiElement().afterLeaf(
StandardPatterns.or(psiElement(KDocTokens.LEADING_ASTERISK), psiElement(KDocTokens.START))),
KDocTagCompletionProvider)
extend(CompletionType.BASIC,
psiElement(KDocTokens.TAG_NAME), KDocTagCompletionProvider)
}
}
object KDocNameCompletionProvider: CompletionProvider<CompletionParameters>() {
override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
val position = parameters.getPosition().getParentOfType<KDocName>(false) ?: return
val declaration = position.getContainingDoc().getOwner() ?: return
val bindingContext = declaration.analyze()
val kdocLink = position.getStrictParentOfType<KDocLink>()!!
val declarationDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration]
if (kdocLink.getTagIfSubject()?.knownTag == KDocKnownTag.PARAM) {
addParamCompletions(position, declarationDescriptor, result)
} else {
val session = KotlinCacheService.getInstance(parameters.getPosition().getProject()).getLazyResolveSession(position)
addLinkCompletions(session, position, declarationDescriptor, result)
}
}
private fun addParamCompletions(position: KDocName,
declarationDescriptor: DeclarationDescriptor,
result: CompletionResultSet) {
val section = position.getContainingSection()
val documentedParameters = section.findTagsByName("param").map { it.getSubjectName() }.toSet()
val descriptors = getParamDescriptors(declarationDescriptor)
.filter { it.getName().asString() !in documentedParameters }
val resolutionFacade = position.getResolutionFacade()
val factory = LookupElementFactory(listOf())
descriptors.forEach {
result.addElement(factory.createLookupElement(resolutionFacade, it, false))
}
}
private fun addLinkCompletions(session: KotlinCodeAnalyzer,
position: KDocName,
declarationDescriptor: DeclarationDescriptor,
result: CompletionResultSet) {
val scope = getResolutionScope(session, declarationDescriptor)
val resolutionFacade = position.getResolutionFacade()
val factory = LookupElementFactory(listOf())
scope.getAllDescriptors().forEach {
val element = factory.createLookupElement(resolutionFacade, it, false)
result.addElement(object: LookupElementDecorator<LookupElement>(element) {
override fun handleInsert(context: InsertionContext?) {
// don't insert any qualified name here
}
})
}
}
}
object KDocTagCompletionProvider: CompletionProvider<CompletionParameters>() {
override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
val prefix = if (parameters.getPosition().getNode().getElementType() == KDocTokens.TAG_NAME)
parameters.getPosition().getText().removeSuffix(KotlinCompletionContributor.DEFAULT_DUMMY_IDENTIFIER)
else
null
val resultWithPrefix = if (prefix != null) result.withPrefixMatcher(prefix) else result
KDocKnownTag.values().forEach {
resultWithPrefix.addElement(LookupElementBuilder.create("@" + it.name().toLowerCase()))
}
}
}
@@ -0,0 +1,230 @@
/*
* 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.psi.filters.*
import com.intellij.psi.filters.position.LeftNeighbour
import org.jetbrains.kotlin.psi.*
import com.intellij.psi.PsiElement
import com.intellij.psi.filters.position.PositionElementFilter
import org.jetbrains.kotlin.idea.completion.handlers.KotlinFunctionInsertHandler
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.psi.PsiErrorElement
import org.jetbrains.kotlin.lexer.JetKeywordToken
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.completion.handlers.KotlinKeywordInsertHandler
import org.jetbrains.kotlin.psi.psiUtil.siblings
import org.jetbrains.kotlin.lexer.JetTokens.*
import org.jetbrains.kotlin.psi.psiUtil.prevLeafSkipWhitespacesAndComments
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import com.intellij.psi.tree.IElementType
import com.intellij.codeInsight.lookup.LookupElement
object KeywordLookupObject
object KeywordCompletion {
private val NON_ACTUAL_KEYWORDS = setOf(REIFIED_KEYWORD,
CAPITALIZED_THIS_KEYWORD,
TYPE_ALIAS_KEYWORD)
private val ALL_KEYWORDS = (KEYWORDS.getTypes() + SOFT_KEYWORDS.getTypes())
.filter { it !in NON_ACTUAL_KEYWORDS }
.map { it as JetKeywordToken }
private val KEYWORD_TO_DUMMY_POSTFIX = mapOf(
OUT_KEYWORD to " X",
FILE_KEYWORD to ":"
)
public fun complete(position: PsiElement, prefix: String, consumer: (LookupElement) -> Unit) {
if (!GENERAL_FILTER.isAcceptable(position, position)) return
val parserFilter = buildFilter(position)
for (keywordToken in ALL_KEYWORDS) {
val keyword = keywordToken.getValue()
if (keyword.startsWith(prefix)/* use simple matching by prefix, not prefix matcher from completion*/ && parserFilter(keywordToken)) {
val element = LookupElementBuilder.create(KeywordLookupObject, keyword)
.bold()
.withInsertHandler(if (keywordToken !in FUNCTION_KEYWORDS)
KotlinKeywordInsertHandler
else
KotlinFunctionInsertHandler.NO_PARAMETERS_HANDLER)
consumer(element)
}
}
}
private val FUNCTION_KEYWORDS = listOf(GET_KEYWORD, SET_KEYWORD, CONSTRUCTOR_KEYWORD)
private val GENERAL_FILTER = NotFilter(OrFilter(
CommentFilter(),
ParentFilter(ClassFilter(javaClass<JetLiteralStringTemplateEntry>())),
ParentFilter(ClassFilter(javaClass<JetConstantExpression>())),
LeftNeighbour(TextFilter(".")),
LeftNeighbour(TextFilter("?."))
))
private class CommentFilter() : ElementFilter {
override fun isAcceptable(element : Any?, context : PsiElement?)
= (element is PsiElement) && JetPsiUtil.isInComment(element as PsiElement)
override fun isClassAcceptable(hintClass: Class<out Any?>)
= true
}
private class ParentFilter(filter : ElementFilter) : PositionElementFilter() {
init {
setFilter(filter)
}
override fun isAcceptable(element : Any?, context : PsiElement?) : Boolean {
val parent = (element as? PsiElement)?.getParent()
return parent != null && (getFilter()?.isAcceptable(parent, context) ?: true)
}
}
private fun buildFilter(position: PsiElement): (JetKeywordToken) -> Boolean {
var parent = position.getParent()
var prevParent = position
while (parent != null) {
val _parent = parent
when (_parent) {
is JetBlockExpression -> {
return buildFilterWithContext("fun foo() { ", prevParent, position)
}
is JetWithExpressionInitializer -> {
val initializer = _parent.getInitializer()
if (prevParent == initializer) {
return buildFilterWithContext("val v = ", initializer, position)
}
}
is JetParameter -> {
val default = _parent.getDefaultValue()
if (prevParent == default) {
return buildFilterWithContext("val v = ", default, position)
}
}
}
if (_parent is JetDeclaration) {
val scope = _parent.getParent()
when (scope) {
is JetClassOrObject -> return buildFilterWithReducedContext("class X { ", _parent, position)
is JetFile -> return buildFilterWithReducedContext("", _parent, position)
}
}
prevParent = _parent
parent = _parent.getParent()
}
return buildFilterWithReducedContext("", null, position)
}
private fun buildFilterWithContext(prefixText: String,
contextElement: PsiElement,
position: PsiElement): (JetKeywordToken) -> Boolean {
val offset = position.getStartOffsetInAncestor(contextElement)
val truncatedContext = contextElement.getText()!!.substring(0, offset)
return buildFilterByText(prefixText + truncatedContext, contextElement.getProject())
}
private fun buildFilterWithReducedContext(prefixText: String,
contextElement: PsiElement?,
position: PsiElement): (JetKeywordToken) -> Boolean {
val builder = StringBuilder()
buildReducedContextBefore(builder, position, contextElement)
return buildFilterByText(prefixText + builder.toString(), position.getProject())
}
private fun buildFilterByText(prefixText: String, project: Project): (JetKeywordToken) -> Boolean {
val psiFactory = JetPsiFactory(project)
return { keywordTokenType ->
val postfix = KEYWORD_TO_DUMMY_POSTFIX[keywordTokenType] ?: ""
val file = psiFactory.createFile(prefixText + keywordTokenType.getValue() + postfix)
val elementAt = file.findElementAt(prefixText.length)!!
when {
!elementAt.getNode()!!.getElementType().matchesKeyword(keywordTokenType) -> false
elementAt.getNonStrictParentOfType<PsiErrorElement>() != null -> false
elementAt.prevLeafSkipWhitespacesAndComments() is PsiErrorElement -> false
else -> true
}
}
}
private fun IElementType.matchesKeyword(keywordType: JetKeywordToken): Boolean {
return when(this) {
keywordType -> true
NOT_IN -> keywordType == IN_KEYWORD
NOT_IS -> keywordType == IS_KEYWORD
else -> false
}
}
// builds text within scope (or from the start of the file) before position element excluding almost all declarations
private fun buildReducedContextBefore(builder: StringBuilder, position: PsiElement, scope: PsiElement?) {
if (position == scope) return
val parent = position.getParent() ?: return
buildReducedContextBefore(builder, parent, scope)
val prevDeclaration = position.siblings(forward = false, withItself = false).firstOrNull { it is JetDeclaration }
var child = parent.getFirstChild()
while (child != position) {
if (child is JetDeclaration) {
if (child == prevDeclaration) {
builder.appendReducedText(child!!)
}
}
else {
builder.append(child!!.getText())
}
child = child!!.getNextSibling()
}
}
private fun StringBuilder.appendReducedText(element: PsiElement) {
var child = element.getFirstChild()
if (child == null) {
append(element.getText()!!)
}
else {
while (child != null) {
when (child) {
is JetBlockExpression, is JetClassBody -> append("{}")
else -> appendReducedText(child)
}
child = child.getNextSibling()
}
}
}
private fun PsiElement.getStartOffsetInAncestor(ancestor: PsiElement): Int {
if (ancestor == this) return 0
return getParent()!!.getStartOffsetInAncestor(ancestor) + getStartOffsetInParent()
}
}
@@ -0,0 +1,97 @@
/*
* 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.lookup.CharFilter
import com.intellij.codeInsight.lookup.Lookup
import com.intellij.codeInsight.lookup.CharFilter.Result
import org.jetbrains.kotlin.psi.JetFile
import com.intellij.openapi.util.Key
import com.intellij.codeInsight.completion.CompletionService
import com.intellij.codeInsight.completion.CompletionProgressIndicator
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.psiUtil.prevLeafSkipWhitespacesAndComments
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.JetFunctionLiteral
public class KotlinCompletionCharFilter() : CharFilter() {
companion object {
public val ACCEPT_OPENING_BRACE: Key<Boolean> = Key("KotlinCompletionCharFilter.ACCEPT_OPENNING_BRACE")
public val JUST_TYPING_PREFIX: Key<String> = Key("KotlinCompletionCharFilter.JUST_TYPING_PREFIX")
}
override fun acceptChar(c : Char, prefixLength : Int, lookup : Lookup) : Result? {
if (lookup.getPsiFile() !is JetFile) return null
if (!lookup.isCompletion()) return null
// it does not work in tests, so we use other way
// val isAutopopup = CompletionService.getCompletionService().getCurrentCompletion().isAutopopupCompletion()
val completionParameters = (CompletionService.getCompletionService().getCurrentCompletion() as CompletionProgressIndicator).getParameters()
val isAutopopup = completionParameters.getInvocationCount() == 0
if (Character.isJavaIdentifierPart(c) || c == '@') {
return CharFilter.Result.ADD_TO_PREFIX
}
// do not accept items by special chars in the very beginning of function literal where name of the first parameter can be
if (isAutopopup && !lookup.isSelectionTouched() && isInFunctionLiteralStart(completionParameters.getPosition())) {
return Result.HIDE_LOOKUP
}
if (c == ':' /* used in '::xxx'*/) {
return CharFilter.Result.ADD_TO_PREFIX
}
val currentItem = lookup.getCurrentItem()
if (!lookup.isSelectionTouched()) {
currentItem?.putUserData(JUST_TYPING_PREFIX, lookup.itemPattern(currentItem))
}
return when (c) {
'.' -> {
if (prefixLength == 0 && isAutopopup && !lookup.isSelectionTouched()) {
val caret = lookup.getEditor().getCaretModel().getOffset()
if (caret > 0 && lookup.getEditor().getDocument().getCharsSequence()[caret - 1] == '.') {
return Result.HIDE_LOOKUP
}
}
Result.SELECT_ITEM_AND_FINISH_LOOKUP
}
'{' -> {
if (currentItem != null && currentItem.getUserData(ACCEPT_OPENING_BRACE) ?: false)
Result.SELECT_ITEM_AND_FINISH_LOOKUP
else
Result.HIDE_LOOKUP
}
',', ' ', '(', '=' -> Result.SELECT_ITEM_AND_FINISH_LOOKUP
else -> CharFilter.Result.HIDE_LOOKUP
}
}
private fun isInFunctionLiteralStart(position: PsiElement): Boolean {
var prev = position.prevLeafSkipWhitespacesAndComments()
if (prev?.getNode()?.getElementType() == JetTokens.LPAR) {
prev = prev?.prevLeafSkipWhitespacesAndComments()
}
if (prev?.getNode()?.getElementType() != JetTokens.LBRACE) return false
val functionLiteral = prev!!.getParent() as? JetFunctionLiteral ?: return false
return functionLiteral.getLBrace() == prev
}
}
@@ -0,0 +1,381 @@
/*
* 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.*
import com.intellij.openapi.editor.Document
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.patterns.PlatformPatterns
import com.intellij.patterns.PsiJavaPatterns.elementType
import com.intellij.patterns.PsiJavaPatterns.psiElement
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiErrorElement
import com.intellij.psi.TokenType
import com.intellij.psi.search.PsiElementProcessor
import com.intellij.psi.tree.TokenSet
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.ProcessingContext
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletion
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.prevLeaf
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
public class KotlinCompletionContributor : CompletionContributor() {
private val AFTER_NUMBER_LITERAL = psiElement().afterLeafSkipping(psiElement().withText(""), psiElement().withElementType(elementType().oneOf(JetTokens.FLOAT_LITERAL, JetTokens.INTEGER_LITERAL)))
private val AFTER_INTEGER_LITERAL_AND_DOT = psiElement().afterLeafSkipping(psiElement().withText("."), psiElement().withElementType(elementType().oneOf(JetTokens.INTEGER_LITERAL)))
companion object {
public val DEFAULT_DUMMY_IDENTIFIER: String = CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + "$" // add '$' to ignore context after the caret
}
init {
val provider = object : CompletionProvider<CompletionParameters>() {
override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
performCompletion(parameters, result)
}
}
extend(CompletionType.BASIC, PlatformPatterns.psiElement(), provider)
extend(CompletionType.SMART, PlatformPatterns.psiElement(), provider)
}
override fun beforeCompletion(context: CompletionInitializationContext) {
val psiFile = context.getFile()
if (psiFile !is JetFile) return
val offset = context.getStartOffset()
val tokenBefore = psiFile.findElementAt(Math.max(0, offset - 1))
val dummyIdentifier = when {
context.getCompletionType() == CompletionType.SMART -> DEFAULT_DUMMY_IDENTIFIER
PackageDirectiveCompletion.ACTIVATION_PATTERN.accepts(tokenBefore) -> PackageDirectiveCompletion.DUMMY_IDENTIFIER
isInFunctionLiteralParameterList(tokenBefore) -> CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED
isInClassHeader(tokenBefore) -> CompletionUtilCore.DUMMY_IDENTIFIER // do not add '$' to not interrupt class declaration parsing
else -> specialExtensionReceiverDummyIdentifier(tokenBefore)
?: specialInTypeArgsDummyIdentifier(tokenBefore)
?: specialInParameterListDummyIdentifier(tokenBefore)
?: DEFAULT_DUMMY_IDENTIFIER
}
context.setDummyIdentifier(dummyIdentifier)
// this code will make replacement offset "modified" and prevents altering it by the code in CompletionProgressIndicator
context.setReplacementOffset(context.getReplacementOffset())
if (context.getCompletionType() == CompletionType.SMART && !isAtEndOfLine(offset, context.getEditor().getDocument()) /* do not use parent expression if we are at the end of line - it's probably parsed incorrectly */) {
val tokenAt = psiFile.findElementAt(Math.max(0, offset))
if (tokenAt != null) {
var parent = tokenAt.getParent()
if (parent is JetExpression && parent !is JetBlockExpression) {
// search expression to be replaced - go up while we are the first child of parent expression
var expression = parent as JetExpression
parent = expression.getParent()
while (parent is JetExpression && parent!!.getFirstChild() == expression) {
expression = parent as JetExpression
parent = expression.getParent()
}
val suggestedReplacementOffset = replacementOffsetByExpression(expression)
if (suggestedReplacementOffset > context.getReplacementOffset()) {
context.setReplacementOffset(suggestedReplacementOffset)
}
context.getOffsetMap().addOffset(SmartCompletion.OLD_ARGUMENTS_REPLACEMENT_OFFSET, expression.getTextRange().getEndOffset())
val argumentList = (expression.getParent() as? JetValueArgument)?.getParent() as? JetValueArgumentList
if (argumentList != null) {
context.getOffsetMap().addOffset(SmartCompletion.MULTIPLE_ARGUMENTS_REPLACEMENT_OFFSET,
argumentList.getRightParenthesis()?.getTextRange()?.getStartOffset() ?: argumentList.getTextRange().getEndOffset())
}
}
}
}
}
private fun replacementOffsetByExpression(expression: JetExpression): Int {
when (expression) {
is JetCallExpression -> {
val calleeExpression = expression.getCalleeExpression()
if (calleeExpression != null) {
return calleeExpression.getTextRange()!!.getEndOffset()
}
}
is JetQualifiedExpression -> {
val selector = expression.getSelectorExpression()
if (selector != null) {
return replacementOffsetByExpression(selector)
}
}
}
return expression.getTextRange()!!.getEndOffset()
}
private fun isInFunctionLiteralParameterList(tokenBefore: PsiElement?): Boolean {
val parameterList = tokenBefore?.parents(false)?.firstOrNull { it is JetParameterList } ?: return false
val parent = parameterList.getParent()
return parent is JetFunctionLiteral && parent.getValueParameterList() == parameterList
}
private fun isInClassHeader(tokenBefore: PsiElement?): Boolean {
val classOrObject = tokenBefore?.parents(false)?.firstIsInstanceOrNull<JetClassOrObject>() ?: return false
val name = classOrObject.getNameIdentifier() ?: return false
val body = classOrObject.getBody() ?: return false
val offset = tokenBefore!!.getTextRange().getStartOffset()
return name.getTextRange().getEndOffset() <= offset && offset <= body.getTextRange().getStartOffset()
}
private val declarationKeywords = TokenSet.create(JetTokens.FUN_KEYWORD, JetTokens.VAL_KEYWORD, JetTokens.VAR_KEYWORD)
private val declarationTokens = TokenSet.orSet(TokenSet.create(JetTokens.IDENTIFIER, JetTokens.LT, JetTokens.GT,
JetTokens.COMMA, JetTokens.DOT, JetTokens.QUEST, JetTokens.COLON,
JetTokens.IN_KEYWORD, JetTokens.OUT_KEYWORD,
JetTokens.LPAR, JetTokens.RPAR, JetTokens.ARROW,
TokenType.ERROR_ELEMENT),
JetTokens.WHITE_SPACE_OR_COMMENT_BIT_SET)
private fun specialExtensionReceiverDummyIdentifier(tokenBefore: PsiElement?): String? {
var token = tokenBefore ?: return null
var ltCount = 0
var gtCount = 0
val builder = StringBuilder()
while (true) {
val tokenType = token.getNode()!!.getElementType()
if (tokenType in declarationKeywords) {
val balance = ltCount - gtCount
if (balance < 0) return null
builder.append(token.getText()!!.reverse())
builder.reverse()
var tail = "X" + ">".repeat(balance) + ".f"
if (tokenType == JetTokens.FUN_KEYWORD) {
tail += "()"
}
builder append tail
val text = builder.toString()
val file = JetPsiFactory(tokenBefore.getProject()).createFile(text)
val declaration = file.getDeclarations().singleOrNull() ?: return null
if (declaration.getTextLength() != text.length) return null
val containsErrorElement = !PsiTreeUtil.processElements(file, PsiElementProcessor<PsiElement>{ it !is PsiErrorElement })
return if (containsErrorElement) null else tail + "$"
}
if (tokenType !in declarationTokens) return null
if (tokenType == JetTokens.LT) ltCount++
if (tokenType == JetTokens.GT) gtCount++
builder.append(token.getText()!!.reverse())
token = PsiTreeUtil.prevLeaf(token) ?: return null
}
}
private fun performCompletion(parameters: CompletionParameters, result: CompletionResultSet) {
val position = parameters.getPosition()
if (position.getContainingFile() !is JetFile) return
if (position.getNonStrictParentOfType<PsiComment>() != null) {
// don't stop here, allow other contributors to run
return
}
if (shouldSuppressCompletion(parameters, result.getPrefixMatcher())) {
result.stopHere()
return
}
if (PackageDirectiveCompletion.perform(parameters, result)) {
result.stopHere()
return
}
try {
result.restartCompletionWhenNothingMatches()
val configuration = CompletionSessionConfiguration(parameters)
if (parameters.getCompletionType() == CompletionType.BASIC) {
val session = BasicCompletionSession(configuration, parameters, result)
if (session.completionKind == BasicCompletionSession.CompletionKind.ANNOTATION_TYPES_OR_PARAMETER_NAME && parameters.isAutoPopup()) {
result.stopHere()
return
}
val somethingAdded = session.complete()
if (!somethingAdded && parameters.getInvocationCount() < 2) {
// Rerun completion if nothing was found
val newConfiguration = CompletionSessionConfiguration(completeNonImportedDeclarations = true,
completeNonAccessibleDeclarations = false)
BasicCompletionSession(newConfiguration, parameters, result).complete()
}
}
else {
SmartCompletionSession(configuration, parameters, result).complete()
}
}
catch (e: ProcessCanceledException) {
throw rethrowWithCancelIndicator(e)
}
}
private fun shouldSuppressCompletion(parameters: CompletionParameters, prefixMatcher: PrefixMatcher): Boolean {
val position = parameters.getPosition()
val invocationCount = parameters.getInvocationCount()
// no completion inside number literals
if (AFTER_NUMBER_LITERAL.accepts(position)) return true
// no completion auto-popup after integer and dot
if (invocationCount == 0 && prefixMatcher.getPrefix().isEmpty() && AFTER_INTEGER_LITERAL_AND_DOT.accepts(position)) return true
// no auto-popup on typing after "val", "var" and "fun" because it's likely the name of the declaration which is being typed by user
if (invocationCount == 0 && isInExtensionReceiver(position)) return true
return false
}
private fun isInExtensionReceiver(position: PsiElement): Boolean {
val nameRef = position.getParent() as? JetNameReferenceExpression ?: return false
val userType = nameRef.getParent() as? JetUserType ?: return false
val typeRef = userType.getParent() as? JetTypeReference ?: return false
if (userType != typeRef.getTypeElement()) return false
val parent = typeRef.getParent()
return when (parent) {
is JetNamedFunction -> typeRef == parent.getReceiverTypeReference()
is JetProperty -> typeRef == parent.getReceiverTypeReference()
else -> false
}
}
private fun isAtEndOfLine(offset: Int, document: Document): Boolean {
var i = offset
val chars = document.getCharsSequence()
while (i < chars.length()) {
val c = chars.charAt(i)
if (c == '\n') return true
if (!Character.isWhitespace(c)) return false
i++
}
return true
}
private fun specialInTypeArgsDummyIdentifier(tokenBefore: PsiElement?): String? {
if (tokenBefore == null) return null
val pair = unclosedTypeArgListNameAndBalance(tokenBefore) ?: return null
val (nameToken, balance) = pair
assert(balance > 0)
val nameRef = nameToken.getParent() as? JetNameReferenceExpression ?: return null
val bindingContext = nameRef.getResolutionFacade().analyze(nameRef, BodyResolveMode.PARTIAL)
val target = bindingContext[BindingContext.REFERENCE_TARGET, nameRef]
val targets = if (target != null) {
listOf(target)
}
else {
bindingContext[BindingContext.AMBIGUOUS_REFERENCE_TARGET, nameRef] ?: return null
}
if (targets.all { it is FunctionDescriptor || it is ClassDescriptor && it.getKind() == ClassKind.CLASS }) {
return CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + ">".repeat(balance) + "$"
}
else {
return null
}
}
private fun unclosedTypeArgListNameAndBalance(tokenBefore: PsiElement): Pair<PsiElement, Int>? {
if (tokenBefore.getParentOfType<JetTypeArgumentList>(true) != null) return null // already parsed inside type argument list
val nameToken = findCallNameTokenIfInTypeArgs(tokenBefore) ?: return null
val pair = unclosedTypeArgListNameAndBalance(nameToken)
if (pair == null) {
return Pair(nameToken, 1)
}
else {
return Pair(pair.first, pair.second + 1)
}
}
private val callTypeArgsTokens = TokenSet.orSet(TokenSet.create(JetTokens.IDENTIFIER, JetTokens.LT, JetTokens.GT,
JetTokens.COMMA, JetTokens.DOT, JetTokens.QUEST, JetTokens.COLON,
JetTokens.LPAR, JetTokens.RPAR, JetTokens.ARROW),
JetTokens.WHITE_SPACE_OR_COMMENT_BIT_SET)
// if the leaf could be located inside type argument list of a call (if parsed properly)
// then it returns the call name reference this type argument list would belong to
private fun findCallNameTokenIfInTypeArgs(leaf: PsiElement): PsiElement? {
var current = leaf
while (true) {
val tokenType = current.getNode()!!.getElementType()
if (tokenType !in callTypeArgsTokens) return null
if (tokenType == JetTokens.LT) {
val nameToken = current.prevLeaf(skipEmptyElements = true) ?: return null
if (nameToken.getNode()!!.getElementType() != JetTokens.IDENTIFIER) return null
return nameToken
}
if (tokenType == JetTokens.GT) { // pass nested type argument list
val prev = current.prevLeaf(skipEmptyElements = true) ?: return null
val typeRef = findCallNameTokenIfInTypeArgs(prev) ?: return null
current = typeRef
continue
}
current = current.prevLeaf(skipEmptyElements = true) ?: return null
}
}
private fun specialInParameterListDummyIdentifier(tokenBefore: PsiElement?): String? {
if (tokenBefore == null) return null
var parent = tokenBefore.getParent()
while (parent != null) {
if (parent is JetParameterList) {
val balance = countParenthesisBalance(tokenBefore, parent)
val count = if (balance > 1) balance - 1 else 0
return CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + ")".repeat(count) + " a: B$"
}
if (parent is JetTypeElement) return null
if (parent is JetAnnotationEntry) return null
parent = parent.getParent()
}
return null
}
private fun countParenthesisBalance(at: PsiElement, container: PsiElement): Int {
val stopAt = container.prevLeaf()
var current: PsiElement? = at
var balance = 0
while (current != stopAt) {
when (current!!.getNode().getElementType()) {
JetTokens.LPAR -> balance++
JetTokens.RPAR -> balance--
}
current = current!!.prevLeaf()
}
return balance
}
}
@@ -0,0 +1,36 @@
/*
* 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.psi.PsiElement
import com.intellij.psi.filters.ClassFilter
import com.intellij.psi.filters.ElementFilter
import com.intellij.psi.impl.source.tree.LeafPsiElement
import com.intellij.psi.tree.IElementType
public class LeafElementFilter(private val elementType: IElementType) : ElementFilter {
override fun isAcceptable(element: Any?, context: PsiElement?)
= element is LeafPsiElement && element.getElementType() == elementType
override fun isClassAcceptable(hintClass: Class<*>)
= LEAF_CLASS_FILTER.isClassAcceptable(hintClass)
companion object {
private val LEAF_CLASS_FILTER = ClassFilter(javaClass<LeafPsiElement>())
}
}
@@ -0,0 +1,290 @@
/*
* 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.InsertHandler
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.openapi.util.Iconable
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.idea.JetDescriptorIconProvider
import org.jetbrains.kotlin.idea.completion.handlers.*
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import com.intellij.psi.PsiClass
import org.jetbrains.kotlin.asJava.KotlinLightClass
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.codeInsight.lookup.LookupElementPresentation
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import com.intellij.codeInsight.lookup.DefaultLookupItemRenderer
import org.jetbrains.kotlin.types.TypeUtils
import com.intellij.codeInsight.lookup.impl.LookupCellRenderer
import org.jetbrains.kotlin.idea.util.nullability
import org.jetbrains.kotlin.idea.util.TypeNullability
public class LookupElementFactory(
private val receiverTypes: Collection<JetType>
) {
public fun createLookupElement(
resolutionFacade: ResolutionFacade,
descriptor: DeclarationDescriptor,
boldImmediateMembers: Boolean
): LookupElement {
val _descriptor = if (descriptor is CallableMemberDescriptor)
DescriptorUtils.unwrapFakeOverride(descriptor)
else
descriptor
var element = createLookupElement(resolutionFacade, _descriptor, DescriptorToSourceUtils.descriptorToDeclaration(_descriptor))
val weight = callableWeight(descriptor)
if (weight != null) {
element.putUserData(CALLABLE_WEIGHT_KEY, weight) // store for use in lookup elements sorting
}
if (boldImmediateMembers) {
element = element.boldIfImmediate(weight)
}
return element
}
private fun LookupElement.boldIfImmediate(weight: CallableWeight?): LookupElement {
val style = when (weight) {
CallableWeight.thisClassMember, CallableWeight.thisTypeExtension -> Style.BOLD
CallableWeight.notApplicableReceiverNullable -> Style.GRAYED
else -> Style.NORMAL
}
return if (style != Style.NORMAL) {
object : LookupElementDecorator<LookupElement>(this) {
override fun renderElement(presentation: LookupElementPresentation) {
super.renderElement(presentation)
if (style == Style.BOLD) {
presentation.setItemTextBold(true)
}
else {
presentation.setItemTextForeground(LookupCellRenderer.getGrayedForeground(false))
// gray all tail fragments too:
val fragments = presentation.getTailFragments()
presentation.clearTail()
for (fragment in fragments) {
presentation.appendTailText(fragment.text, true)
}
}
}
}
}
else {
this
}
}
private enum class Style {
NORMAL
BOLD
GRAYED
}
public fun createLookupElementForJavaClass(psiClass: PsiClass): LookupElement {
var element = LookupElementBuilder.create(psiClass, psiClass.getName()).withInsertHandler(KotlinClassInsertHandler)
val typeParams = psiClass.getTypeParameters()
if (typeParams.isNotEmpty()) {
element = element.appendTailText(typeParams.map { it.getName() }.joinToString(", ", "<", ">"), true)
}
val qualifiedName = psiClass.getQualifiedName()!!
val packageName = qualifiedName.substringBeforeLast('.', "<root>")
element = element.appendTailText(" ($packageName)", true)
if (psiClass.isDeprecated()) {
element = element.setStrikeout(true)
}
// add icon in renderElement only to pass presentation.isReal()
return object : LookupElementDecorator<LookupElement>(element) {
override fun renderElement(presentation: LookupElementPresentation) {
super.renderElement(presentation)
presentation.setIcon(DefaultLookupItemRenderer.getRawIcon(element, presentation.isReal()))
}
}
}
private fun createLookupElement(
resolutionFacade: ResolutionFacade,
descriptor: DeclarationDescriptor,
declaration: PsiElement?
): LookupElement {
if (descriptor is ClassifierDescriptor &&
declaration is PsiClass &&
declaration !is KotlinLightClass) {
// for java classes we create special lookup elements
// because they must be equal to ones created in TypesCompletion
// otherwise we may have duplicates
return createLookupElementForJavaClass(declaration)
}
// for constructor use name and icon of containing class
val nameAndIconDescriptor: DeclarationDescriptor
val iconDeclaration: PsiElement?
if (descriptor is ConstructorDescriptor) {
nameAndIconDescriptor = descriptor.getContainingDeclaration()
iconDeclaration = DescriptorToSourceUtils.descriptorToDeclaration(nameAndIconDescriptor)
}
else {
nameAndIconDescriptor = descriptor
iconDeclaration = declaration
}
val name = nameAndIconDescriptor.getName().asString()
val icon = JetDescriptorIconProvider.getIcon(nameAndIconDescriptor, iconDeclaration, Iconable.ICON_FLAG_VISIBILITY)
var element = LookupElementBuilder.create(DeclarationDescriptorLookupObject(descriptor, resolutionFacade, declaration), name)
.withIcon(icon)
when (descriptor) {
is FunctionDescriptor -> {
val returnType = descriptor.getReturnType()
element = element.withTypeText(if (returnType != null) DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(returnType) else "")
element = element.appendTailText(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderFunctionParameters(descriptor), false)
}
is VariableDescriptor -> {
element = element.withTypeText(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(descriptor.getType()))
}
is ClassDescriptor -> {
val typeParams = descriptor.getTypeConstructor().getParameters()
if (typeParams.isNotEmpty()) {
element = element.appendTailText(typeParams.map { it.getName().asString() }.joinToString(", ", "<", ">"), true)
}
element = element.appendTailText(" (" + DescriptorUtils.getFqName(descriptor.getContainingDeclaration()) + ")", true)
}
else -> {
element = element.withTypeText(DescriptorRenderer.SHORT_NAMES_IN_TYPES.render(descriptor))
}
}
if (descriptor is CallableDescriptor) {
if (descriptor.getExtensionReceiverParameter() != null) {
val container = descriptor.getContainingDeclaration()
val containerPresentation = if (container is ClassDescriptor) {
DescriptorUtils.getFqNameFromTopLevelClass(container).toString()
}
else {
DescriptorUtils.getFqName(container).toString()
}
val originalReceiver = descriptor.getOriginal().getExtensionReceiverParameter()!!
val receiverPresentation = DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(originalReceiver.getType())
element = element.appendTailText(" for $receiverPresentation in $containerPresentation", true)
}
else {
val container = descriptor.getContainingDeclaration()
if (container is PackageFragmentDescriptor) { // we show container only for global functions and properties
//TODO: it would be probably better to show it also for static declarations which are not from the current class (imported)
element = element.appendTailText(" (${container.fqName})", true)
}
}
}
if (KotlinBuiltIns.isDeprecated(descriptor)) {
element = element.withStrikeoutness(true)
}
val insertHandler = getDefaultInsertHandler(descriptor)
element = element.withInsertHandler(insertHandler)
if (insertHandler is KotlinFunctionInsertHandler && insertHandler.lambdaInfo != null) {
element.putUserData<Boolean>(KotlinCompletionCharFilter.ACCEPT_OPENING_BRACE, true)
}
return element
}
private fun callableWeight(descriptor: DeclarationDescriptor): CallableWeight? {
if (descriptor !is CallableDescriptor) return null
val isReceiverNullable = receiverTypes.isNotEmpty() && receiverTypes.all { it.nullability() == TypeNullability.NULLABLE }
val receiverParameter = descriptor.getExtensionReceiverParameter()
if (receiverParameter != null) {
val receiverParamType = receiverParameter.getType()
return if (isReceiverNullable && receiverParamType.nullability() == TypeNullability.NOT_NULL)
CallableWeight.notApplicableReceiverNullable
else if (receiverTypes.any { TypeUtils.equalTypes(it, receiverParamType) })
CallableWeight.thisTypeExtension
else
CallableWeight.baseTypeExtension
}
else {
if (isReceiverNullable) {
return CallableWeight.notApplicableReceiverNullable
}
else {
val container = descriptor.getContainingDeclaration()
return when (container) {
is PackageFragmentDescriptor -> CallableWeight.global
is ClassifierDescriptor -> {
if ((descriptor as CallableMemberDescriptor).getKind() == CallableMemberDescriptor.Kind.DECLARATION)
CallableWeight.thisClassMember
else
CallableWeight.baseClassMember
}
else -> CallableWeight.local
}
}
}
}
companion object {
public fun getDefaultInsertHandler(descriptor: DeclarationDescriptor): InsertHandler<LookupElement> {
return when (descriptor) {
is FunctionDescriptor -> {
val parameters = descriptor.getValueParameters()
when (parameters.size()) {
0 -> KotlinFunctionInsertHandler.NO_PARAMETERS_HANDLER
1 -> {
val parameterType = parameters.single().getType()
if (KotlinBuiltIns.isFunctionOrExtensionFunctionType(parameterType)) {
val parameterCount = KotlinBuiltIns.getParameterTypeProjectionsFromFunctionType(parameterType).size()
if (parameterCount <= 1) {
// otherwise additional item with lambda template is to be added
return KotlinFunctionInsertHandler(CaretPosition.IN_BRACKETS, GenerateLambdaInfo(parameterType, false))
}
}
KotlinFunctionInsertHandler.WITH_PARAMETERS_HANDLER
}
else -> KotlinFunctionInsertHandler.WITH_PARAMETERS_HANDLER
}
}
is PropertyDescriptor -> KotlinPropertyInsertHandler
is ClassDescriptor -> KotlinClassInsertHandler
else -> BaseDeclarationInsertHandler()
}
}
}
}
@@ -0,0 +1,170 @@
/*
* 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.CompletionParameters
import com.intellij.codeInsight.completion.CompletionResultSet
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.codeInsight.lookup.LookupElementPresentation
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.completion.handlers.*
import java.util.ArrayList
class LookupElementsCollector(
private val prefixMatcher: PrefixMatcher,
private val completionParameters: CompletionParameters,
private val resolutionFacade: ResolutionFacade,
private val lookupElementFactory: LookupElementFactory,
private val inDescriptor: DeclarationDescriptor?,
private val surroundCallsWithBraces: Boolean
) {
private val elements = ArrayList<LookupElement>()
public fun flushToResultSet(resultSet: CompletionResultSet) {
if (!elements.isEmpty()) {
resultSet.addAllElements(elements)
elements.clear()
isResultEmpty = false
}
}
public var isResultEmpty: Boolean = true
private set
public fun addDescriptorElements(descriptors: Iterable<DeclarationDescriptor>,
suppressAutoInsertion: Boolean, // auto-insertion suppression is used for elements that require adding an import
withReceiverCast: Boolean = false
) {
for (descriptor in descriptors) {
addDescriptorElements(descriptor, suppressAutoInsertion, withReceiverCast)
}
}
public fun addDescriptorElements(descriptor: DeclarationDescriptor, suppressAutoInsertion: Boolean, withReceiverCast: Boolean) {
run {
var lookupElement = lookupElementFactory.createLookupElement(resolutionFacade, descriptor, true)
if (withReceiverCast) {
lookupElement = lookupElement.withReceiverCast()
}
if (surroundCallsWithBraces && (descriptor is FunctionDescriptor || descriptor is ClassifierDescriptor)) {
lookupElement = lookupElement.withBracesSurrounding()
}
if (suppressAutoInsertion) {
addElementWithAutoInsertionSuppressed(lookupElement)
}
else {
addElement(lookupElement)
}
}
// add special item for function with one argument of function type with more than one parameter
if (descriptor is FunctionDescriptor) {
val parameters = descriptor.getValueParameters()
if (parameters.size() == 1) {
val parameterType = parameters.get(0).getType()
if (KotlinBuiltIns.isFunctionOrExtensionFunctionType(parameterType)) {
val parameterCount = KotlinBuiltIns.getParameterTypeProjectionsFromFunctionType(parameterType).size()
if (parameterCount > 1) {
var lookupElement = lookupElementFactory.createLookupElement(resolutionFacade, descriptor, true)
lookupElement = object : LookupElementDecorator<LookupElement>(lookupElement) {
override fun renderElement(presentation: LookupElementPresentation) {
super.renderElement(presentation)
val tails = presentation.getTailFragments()
presentation.clearTail()
presentation.appendTailText(" " + buildLambdaPresentation(parameterType), false)
tails.drop(1)/*drop old function signature*/.forEach { presentation.appendTailText(it.text, it.isGrayed()) }
}
override fun handleInsert(context: InsertionContext) {
KotlinFunctionInsertHandler(CaretPosition.IN_BRACKETS, GenerateLambdaInfo(parameterType, true)).handleInsert(context, this)
}
}
if (surroundCallsWithBraces) {
lookupElement = lookupElement.withBracesSurrounding()
}
addElement(lookupElement)
}
}
}
}
if (descriptor is PropertyDescriptor) {
var lookupElement = lookupElementFactory.createBackingFieldLookupElement(descriptor, inDescriptor, resolutionFacade)
if (lookupElement != null) {
if (surroundCallsWithBraces) {
lookupElement = lookupElement!!.withBracesSurrounding()
}
addElement(lookupElement!!)
}
}
}
public fun addElement(element: LookupElement) {
if (prefixMatcher.prefixMatches(element)) {
elements.add(object: LookupElementDecorator<LookupElement>(element) {
override fun handleInsert(context: InsertionContext) {
getDelegate().handleInsert(context)
if (context.shouldAddCompletionChar() && !isJustTyping(context, this)) {
val handler = when (context.getCompletionChar()) {
',' -> WithTailInsertHandler.commaTail()
'=' -> WithTailInsertHandler.eqTail()
else -> null
}
handler?.postHandleInsert(context, getDelegate())
}
}
})
}
}
// used to avoid insertion of spaces before/after ',', '=' on just typing
private fun isJustTyping(context: InsertionContext, element: LookupElement): Boolean {
if (!completionParameters.isAutoPopup()) return false
val insertedText = context.getDocument().getText(TextRange(context.getStartOffset(), context.getTailOffset()))
return insertedText == element.getUserData(KotlinCompletionCharFilter.JUST_TYPING_PREFIX)
}
public fun addElementWithAutoInsertionSuppressed(element: LookupElement) {
if (isResultEmpty && elements.isEmpty()) { /* without these checks we may get duplicated items */
addElement(element.suppressAutoInsertion())
}
else {
addElement(element)
}
}
public fun addElements(elements: Iterable<LookupElement>) {
elements.forEach { addElement(it) }
}
}
@@ -0,0 +1,115 @@
/*
* 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.InsertHandler
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.psi.PsiElement
import com.intellij.psi.filters.AndFilter
import com.intellij.psi.filters.ClassFilter
import com.intellij.psi.filters.OrFilter
import com.intellij.psi.filters.position.ParentElementFilter
import org.jetbrains.kotlin.core.FirstChildInParentFilter
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.JetIcons
import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler
import org.jetbrains.kotlin.idea.core.quickfix.QuickFixUtil
import org.jetbrains.kotlin.idea.references.JetReference
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.JetCallElement
import org.jetbrains.kotlin.psi.JetValueArgument
import org.jetbrains.kotlin.psi.JetValueArgumentName
import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext
object NamedParametersCompletion {
private val positionFilter = AndFilter(
LeafElementFilter(JetTokens.IDENTIFIER),
OrFilter(
AndFilter(
ParentElementFilter(ClassFilter(javaClass<JetValueArgument>()), 2),
FirstChildInParentFilter(2)
),
ParentElementFilter(ClassFilter(javaClass<JetValueArgumentName>()), 2)
)
)
public fun isOnlyNamedParameterExpected(position: PsiElement): Boolean {
if (!positionFilter.isAcceptable(position, position)) return false
val thisArgument = position.getStrictParentOfType<JetValueArgument>()!!
val callElement = thisArgument.getStrictParentOfType<JetCallElement>() ?: return false
for (argument in callElement.getValueArguments()) {
if (argument == thisArgument) break
if (argument.isNamed()) return true
}
return false
}
public fun complete(position: PsiElement, collector: LookupElementsCollector, bindingContext: BindingContext) {
if (!positionFilter.isAcceptable(position, position)) return
val valueArgument = position.getStrictParentOfType<JetValueArgument>()!!
val callElement = valueArgument.getStrictParentOfType<JetCallElement>() ?: return
val callSimpleName = callElement.getCallNameExpression() ?: return
val callReference = callSimpleName.getReference() as JetReference
val functionDescriptors = callReference.resolveToDescriptors(bindingContext).map { it as? FunctionDescriptor }.filterNotNull()
for (funDescriptor in functionDescriptors) {
if (!funDescriptor.hasStableParameterNames()) continue
val usedArguments = QuickFixUtil.getUsedParameters(callElement, valueArgument, funDescriptor)
for (parameter in funDescriptor.getValueParameters()) {
val name = parameter.getName()
val nameString = name.asString()
if (nameString !in usedArguments) {
val lookupElement = LookupElementBuilder.create(nameString)
.withPresentableText("$nameString =")
.withTailText(" ${DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(parameter.getType())}")
.withIcon(JetIcons.PARAMETER)
.withInsertHandler(NamedParameterInsertHandler(name))
.assignPriority(ItemPriority.NAMED_PARAMETER)
collector.addElement(lookupElement)
}
}
}
}
private class NamedParameterInsertHandler(val parameterName: Name) : InsertHandler<LookupElement> {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
val editor = context.getEditor()
val text = IdeDescriptorRenderers.SOURCE_CODE.renderName(parameterName)
editor.getDocument().replaceString(context.getStartOffset(), context.getTailOffset(), text)
editor.getCaretModel().moveToOffset(context.getStartOffset() + text.length)
WithTailInsertHandler.eqTail().postHandleInsert(context, item)
}
}
}
@@ -0,0 +1,67 @@
/*
* 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.*
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.patterns.PlatformPatterns
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetPackageDirective
import org.jetbrains.kotlin.idea.caches.resolve.*
import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
/**
* Performs completion in package directive. Should suggest only packages and avoid showing fake package produced by
* DUMMY_IDENTIFIER.
*/
object PackageDirectiveCompletion {
val DUMMY_IDENTIFIER = "___package___"
val ACTIVATION_PATTERN = PlatformPatterns.psiElement().inside(javaClass<JetPackageDirective>())
fun perform(parameters: CompletionParameters, result: CompletionResultSet): Boolean {
val position = parameters.getPosition()
if (!ACTIVATION_PATTERN.accepts(position)) return false
val file = position.getContainingFile() as JetFile
val ref = file.findReferenceAt(parameters.getOffset()) as? JetSimpleNameReference ?: return false
val name = ref.expression.getText()!!
try {
val prefixLength = parameters.getOffset() - ref.expression.getTextOffset()
val prefixMatcher = PlainPrefixMatcher(name.substring(0, prefixLength))
val result = result.withPrefixMatcher(prefixMatcher)
val resolutionFacade = ref.expression.getResolutionFacade()
val bindingContext = resolutionFacade.analyze(ref.expression)
val variants = ReferenceVariantsHelper(bindingContext, { true }).getPackageReferenceVariants(ref.expression, prefixMatcher.asNameFilter())
for (variant in variants) {
val lookupElement = LookupElementFactory(listOf()).createLookupElement(resolutionFacade, variant, false)
if (!lookupElement.getLookupString().contains(DUMMY_IDENTIFIER)) {
result.addElement(lookupElement)
}
}
return true
}
catch (e: ProcessCanceledException) {
throw rethrowWithCancelIndicator(e)
}
}
}
@@ -0,0 +1,76 @@
/*
* 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 org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetDeclaration
import com.intellij.psi.util.PsiTreeUtil
class ToFromOriginalFileMapper(
val originalFile: JetFile,
val syntheticFile: JetFile,
val completionOffset: Int
) {
private val syntheticLength: Int
private val originalLength: Int
private val tailLength: Int
private val shift: Int
//TODO: lazy initialization?
init {
val originalText = originalFile.getText()
val syntheticText = syntheticFile.getText()
assert(originalText.subSequence(0, completionOffset) == syntheticText.subSequence(0, completionOffset)) //TODO: drop it
syntheticLength = syntheticText.length
originalLength = originalText.length
val minLength = Math.min(originalLength, syntheticLength)
tailLength = (0..minLength-1).firstOrNull {
syntheticText[syntheticLength - it - 1] != originalText[originalLength - it - 1]
} ?: minLength
shift = syntheticLength - originalLength
}
public fun toOriginalFile(offset: Int): Int? {
return when {
offset <= completionOffset -> offset
offset >= syntheticLength - tailLength -> offset - shift
else -> null
}
}
public fun toSyntheticFile(offset: Int): Int? {
return when {
offset <= completionOffset -> offset
offset >= originalLength - tailLength -> offset + shift
else -> null
}
}
public fun toOriginalFile(declaration: JetDeclaration): JetDeclaration? {
if (declaration.getContainingFile() != syntheticFile) return declaration
val offset = toOriginalFile(declaration.getTextRange().getStartOffset()) ?: return null
return PsiTreeUtil.findElementOfClassAtOffset(originalFile, offset, javaClass<JetDeclaration>(), true)
}
public fun toSyntheticFile(declaration: JetDeclaration): JetDeclaration? {
if (declaration.getContainingFile() != originalFile) return declaration
val offset = toSyntheticFile(declaration.getTextRange().getStartOffset()) ?: return null
return PsiTreeUtil.findElementOfClassAtOffset(syntheticFile, offset, javaClass<JetDeclaration>(), true)
}
}
@@ -0,0 +1,73 @@
/*
* 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.confidence;
import com.intellij.codeInsight.completion.CompletionConfidence;
import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ThreeState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.psi.*;
public class UnfocusedPossibleFunctionParameter extends CompletionConfidence {
@NotNull
@Override
public ThreeState shouldFocusLookup(@NotNull CompletionParameters parameters) {
// 1. Do not automatically insert completion for first reference expression in block inside
// function literal if it has no parameters yet.
// 2. The same but for the case when first expression is additionally surrounded with brackets
PsiElement position = parameters.getPosition();
JetFunctionLiteralExpression functionLiteral = PsiTreeUtil.getParentOfType(
position, JetFunctionLiteralExpression.class);
if (functionLiteral != null) {
PsiElement expectedReference = position.getParent();
if (expectedReference instanceof JetSimpleNameExpression) {
if (PsiTreeUtil.findChildOfType(functionLiteral, JetParameterList.class) == null) {
{
// 1.
PsiElement expectedBlock = expectedReference.getParent();
if (expectedBlock instanceof JetBlockExpression) {
if (expectedReference.getPrevSibling() == null) {
return ThreeState.NO;
}
}
}
{
// 2.
PsiElement expectedParenthesized = expectedReference.getParent();
if (expectedParenthesized instanceof JetParenthesizedExpression) {
PsiElement expectedBlock = expectedParenthesized.getParent();
if (expectedBlock instanceof JetBlockExpression) {
if (expectedParenthesized.getPrevSibling() == null) {
return ThreeState.NO;
}
}
}
}
}
}
}
return ThreeState.UNSURE;
}
}
@@ -0,0 +1,40 @@
/*
* 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.handlers
import com.intellij.codeInsight.completion.InsertHandler
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.completion.InsertionContext
import org.jetbrains.kotlin.idea.completion.DeclarationDescriptorLookupObject
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
open class BaseDeclarationInsertHandler : InsertHandler<LookupElement> {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
val descriptor = (item.getObject() as? DeclarationDescriptorLookupObject)?.descriptor
if (descriptor != null) {
val name = descriptor.getName()
val nameInCode = IdeDescriptorRenderers.SOURCE_CODE.renderName(name)
val document = context.getDocument()
val needEscaping = nameInCode != name.asString()
// we check that text inserted matches the name because something else can be inserted by custom insert handler
if (needEscaping && document.getText(TextRange(context.getStartOffset(), context.getTailOffset())) == name.asString()) {
document.replaceString(context.getStartOffset(), context.getTailOffset(), nameInCode)
}
}
}
}
@@ -0,0 +1,137 @@
/*
* 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.handlers
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.codeInsight.template.*
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.completion.ExpectedInfos
import org.jetbrains.kotlin.idea.core.refactoring.EmptyValidator
import org.jetbrains.kotlin.idea.core.refactoring.JetNameSuggester
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.JetExpression
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.JetType
fun insertLambdaTemplate(context: InsertionContext, placeholderRange: TextRange, lambdaType: JetType) {
val explicitParameterTypes = needExplicitParameterTypes(context, placeholderRange, lambdaType)
// we start template later to not interfere with insertion of tail type
val commandProcessor = CommandProcessor.getInstance()
val commandName = commandProcessor.getCurrentCommandName()
val commandGroupId = commandProcessor.getCurrentCommandGroupId()
val rangeMarker = context.getDocument().createRangeMarker(placeholderRange)
context.setLaterRunnable {
commandProcessor.executeCommand(context.getProject(), {
runWriteAction {
try {
if (rangeMarker.isValid()) {
context.getDocument().deleteString(rangeMarker.getStartOffset(), rangeMarker.getEndOffset())
context.getEditor().getCaretModel().moveToOffset(rangeMarker.getStartOffset())
val template = buildTemplate(lambdaType, explicitParameterTypes, context.getProject())
TemplateManager.getInstance(context.getProject()).startTemplate(context.getEditor(), template)
}
}
finally {
rangeMarker.dispose()
}
}
}, commandName, commandGroupId)
}
}
fun buildLambdaPresentation(lambdaType: JetType): String {
val parameterTypes = functionParameterTypes(lambdaType)
val parametersPresentation = parameterTypes.map { IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(it) }.joinToString(", ")
fun wrap(s: String) = if (parameterTypes.size() != 1) "($s)" else s
return "{ ${wrap(parametersPresentation)} -> ... }"
}
private fun needExplicitParameterTypes(context: InsertionContext, placeholderRange: TextRange, lambdaType: JetType): Boolean {
PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments()
val file = context.getFile() as JetFile
val expression = PsiTreeUtil.findElementOfClassAtRange(file, placeholderRange.getStartOffset(), placeholderRange.getEndOffset(), javaClass<JetExpression>())
if (expression == null) return false
val resolutionFacade = file.getResolutionFacade()
val bindingContext = resolutionFacade.analyze(expression, BodyResolveMode.PARTIAL)
val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade, resolutionFacade.findModuleDescriptor(file), false).calculate(expression) ?: return false
val functionTypes = expectedInfos.map { it.type }.filter { KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(it) }.toSet()
if (functionTypes.size() <= 1) return false
val lambdaParameterCount = KotlinBuiltIns.getParameterTypeProjectionsFromFunctionType(lambdaType).size()
return functionTypes.filter { KotlinBuiltIns.getParameterTypeProjectionsFromFunctionType(it).size() == lambdaParameterCount }.size() > 1
}
private fun buildTemplate(lambdaType: JetType, explicitParameterTypes: Boolean, project: Project): Template {
val parameterTypes = functionParameterTypes(lambdaType)
val useParenthesis = explicitParameterTypes || parameterTypes.size() != 1
val manager = TemplateManager.getInstance(project)
val template = manager.createTemplate("", "")
template.setToShortenLongNames(true)
//template.setToReformat(true) //TODO
template.addTextSegment("{ ")
if (useParenthesis) {
template.addTextSegment("(")
}
for ((i, parameterType) in parameterTypes.withIndex()) {
if (i > 0) {
template.addTextSegment(", ")
}
//TODO: check for names in scope
template.addVariable(ParameterNameExpression(JetNameSuggester.suggestNames(parameterType, EmptyValidator, "p")), true)
if (explicitParameterTypes) {
template.addTextSegment(": " + IdeDescriptorRenderers.SOURCE_CODE.renderType(parameterType))
}
}
if (useParenthesis) {
template.addTextSegment(")")
}
template.addTextSegment(" -> ")
template.addEndVariable()
template.addTextSegment(" }")
return template
}
private class ParameterNameExpression(val nameSuggestions: Array<String>) : Expression() {
override fun calculateResult(context: ExpressionContext?) = TextResult(nameSuggestions[0])
override fun calculateQuickResult(context: ExpressionContext?): Result? = null
override fun calculateLookupItems(context: ExpressionContext?)
= Array<LookupElement>(nameSuggestions.size(), { LookupElementBuilder.create(nameSuggestions[it]) })
}
fun functionParameterTypes(functionType: JetType): List<JetType>
= KotlinBuiltIns.getParameterTypeProjectionsFromFunctionType(functionType).map { it.getType() }
@@ -0,0 +1,262 @@
/*
* 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.handlers
import com.intellij.codeInsight.AutoPopupController
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.Lookup
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Document
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.codeStyle.CodeStyleSettingsManager
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.idea.completion.DeclarationDescriptorLookupObject
import org.jetbrains.kotlin.idea.completion.isAfterDot
import org.jetbrains.kotlin.idea.core.formatter.JetCodeStyleSettings
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.types.JetType
public abstract class KotlinCallableInsertHandler : BaseDeclarationInsertHandler() {
public override fun handleInsert(context: InsertionContext, item: LookupElement) {
super.handleInsert(context, item)
addImport(context, item)
}
private fun addImport(context : InsertionContext, item : LookupElement) {
PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments()
ApplicationManager.getApplication()?.runReadAction {
val startOffset = context.getStartOffset()
val element = context.getFile().findElementAt(startOffset)
if (element == null) return@runReadAction
val file = context.getFile()
val o = item.getObject()
if (file is JetFile && o is DeclarationDescriptorLookupObject) {
val descriptor = o.descriptor as? CallableDescriptor
if (descriptor != null) {
// for completion after dot, import insertion may be required only for extensions
if (context.isAfterDot() && descriptor.getExtensionReceiverParameter() == null) {
return@runReadAction
}
if (DescriptorUtils.isTopLevelDeclaration(descriptor)) {
ApplicationManager.getApplication()?.runWriteAction {
ImportInsertHelper.getInstance(context.getProject()).importDescriptor(file, descriptor)
}
}
}
}
}
}
}
public object KotlinPropertyInsertHandler : KotlinCallableInsertHandler()
public enum class CaretPosition {
IN_BRACKETS
AFTER_BRACKETS
}
public data class GenerateLambdaInfo(val lambdaType: JetType, val explicitParameters: Boolean)
public class KotlinFunctionInsertHandler(val caretPosition : CaretPosition, val lambdaInfo: GenerateLambdaInfo?) : KotlinCallableInsertHandler() {
init {
if (caretPosition == CaretPosition.AFTER_BRACKETS && lambdaInfo != null) {
throw IllegalArgumentException("CaretPosition.AFTER_BRACKETS with lambdaInfo != null combination is not supported")
}
}
public override fun handleInsert(context: InsertionContext, item: LookupElement) {
super.handleInsert(context, item)
val psiDocumentManager = PsiDocumentManager.getInstance(context.getProject())
psiDocumentManager.commitAllDocuments()
psiDocumentManager.doPostponedOperationsAndUnblockDocument(context.getDocument())
val startOffset = context.getStartOffset()
val element = context.getFile().findElementAt(startOffset) ?: return
when {
element.getStrictParentOfType<JetImportDirective>() != null -> return
isInfixCall(element) -> {
if (context.getCompletionChar() == ' ') {
context.setAddCompletionChar(false)
}
val tailOffset = context.getTailOffset()
context.getDocument().insertString(tailOffset, " ")
context.getEditor().getCaretModel().moveToOffset(tailOffset + 1)
}
else -> addBrackets(context, element)
}
}
private fun isInfixCall(context: PsiElement): Boolean {
val parent = context.getParent()
val grandParent = parent?.getParent()
return parent is JetSimpleNameExpression && grandParent is JetBinaryExpression && parent == grandParent.getOperationReference()
}
private fun addBrackets(context : InsertionContext, offsetElement : PsiElement) {
val completionChar = context.getCompletionChar()
if (completionChar == '(') { //TODO: more correct behavior related to braces type
context.setAddCompletionChar(false)
}
var offset = context.getTailOffset()
val document = context.getDocument()
val chars = document.getCharsSequence()
val forceParenthesis = lambdaInfo != null && completionChar == '\t' && chars.charAt(offset) == '('
val braces = lambdaInfo != null && completionChar != '(' && !forceParenthesis
val openingBracket = if (braces) '{' else '('
val closingBracket = if (braces) '}' else ')'
if (completionChar == Lookup.REPLACE_SELECT_CHAR) {
val offset1 = skipSpaces(chars, offset)
if (offset1 < document.getTextLength()) {
if (chars[offset1] == '<') {
PsiDocumentManager.getInstance(context.getProject()).commitDocument(document)
val token = context.getFile().findElementAt(offset1)!!
if (token.getNode().getElementType() == JetTokens.LT) {
val parent = token.getParent()
if (parent is JetTypeArgumentList && parent.getText().indexOf('\n') < 0/* if type argument list is on multiple lines this is more likely wrong parsing*/) {
offset = parent.getTextRange().getEndOffset()
}
}
}
}
}
var openingBracketOffset = indexOfSkippingSpace(document, openingBracket, offset)
var inBracketsShift = 0
if (openingBracketOffset == -1) {
if (braces) {
if (completionChar == ' ' || completionChar == '{') {
context.setAddCompletionChar(false)
}
if (isInsertSpacesInOneLineFunctionEnabled(context.getProject())) {
document.insertString(offset, " { }")
inBracketsShift = 1
}
else {
document.insertString(offset, " {}")
}
}
else {
document.insertString(offset, "()")
}
PsiDocumentManager.getInstance(context.getProject()).commitDocument(document)
}
openingBracketOffset = indexOfSkippingSpace(document, openingBracket, offset)
assert(openingBracketOffset != -1, "If there wasn't open bracket it should already have been inserted")
val closeBracketOffset = indexOfSkippingSpace(document, closingBracket, openingBracketOffset + 1)
val editor = context.getEditor()
if (shouldPlaceCaretInBrackets(completionChar) || closeBracketOffset == -1) {
editor.getCaretModel().moveToOffset(openingBracketOffset + 1 + inBracketsShift)
AutoPopupController.getInstance(context.getProject())?.autoPopupParameterInfo(editor, offsetElement)
}
else {
editor.getCaretModel().moveToOffset(closeBracketOffset + 1)
}
PsiDocumentManager.getInstance(context.getProject()).commitDocument(document)
if (lambdaInfo != null && lambdaInfo.explicitParameters) {
insertLambdaTemplate(context, TextRange(openingBracketOffset, closeBracketOffset + 1), lambdaInfo.lambdaType)
}
}
private fun shouldPlaceCaretInBrackets(completionChar: Char): Boolean {
if (completionChar == ',' || completionChar == '.' || completionChar == '=') return false
if (completionChar == '(') return true
return caretPosition == CaretPosition.IN_BRACKETS
}
companion object {
public val NO_PARAMETERS_HANDLER: KotlinFunctionInsertHandler = KotlinFunctionInsertHandler(CaretPosition.AFTER_BRACKETS, null)
public val WITH_PARAMETERS_HANDLER: KotlinFunctionInsertHandler = KotlinFunctionInsertHandler(CaretPosition.IN_BRACKETS, null)
private fun indexOfSkippingSpace(document: Document, ch : Char, startIndex : Int) : Int {
val text = document.getCharsSequence()
for (i in startIndex..text.length() - 1) {
val currentChar = text[i]
if (ch == currentChar) return i
if (currentChar != ' ' && currentChar != '\t') return -1
}
return -1
}
private fun skipSpaces(chars: CharSequence, index : Int) : Int
= (index..chars.length() - 1).firstOrNull { val c = chars[it]; c != ' ' && c != '\t' } ?: chars.length()
private fun isInsertSpacesInOneLineFunctionEnabled(project : Project)
= CodeStyleSettingsManager.getSettings(project)
.getCustomSettings(javaClass<JetCodeStyleSettings>())!!.INSERT_WHITESPACES_IN_SIMPLE_ONE_LINE_METHOD
}
}
object CastReceiverInsertHandler : KotlinCallableInsertHandler() {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
super.handleInsert(context, item)
val expression = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), javaClass<JetSimpleNameExpression>(), false)
val qualifiedExpression = PsiTreeUtil.getParentOfType(expression, javaClass<JetQualifiedExpression>(), true)
if (qualifiedExpression != null) {
val receiver = qualifiedExpression.getReceiverExpression()
val descriptor = (item.getObject() as? DeclarationDescriptorLookupObject)?.descriptor as CallableDescriptor
val project = context.getProject()
val thisObj = if (descriptor.getExtensionReceiverParameter() != null) descriptor.getExtensionReceiverParameter() else descriptor.getDispatchReceiverParameter()
val fqName = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(thisObj.getType().getConstructor().getDeclarationDescriptor())
val parentCast = JetPsiFactory(project).createExpression("(expr as $fqName)") as JetParenthesizedExpression
val cast = parentCast.getExpression() as JetBinaryExpressionWithTypeRHS
cast.getLeft().replace(receiver)
val psiDocumentManager = PsiDocumentManager.getInstance(project)
psiDocumentManager.commitAllDocuments()
psiDocumentManager.doPostponedOperationsAndUnblockDocument(context.getDocument())
val expr = receiver.replace(parentCast) as JetParenthesizedExpression
ShortenReferences.DEFAULT.process((expr.getExpression() as JetBinaryExpressionWithTypeRHS).getRight())
}
}
}
@@ -0,0 +1,91 @@
/*
* 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.handlers
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.idea.completion.DeclarationDescriptorLookupObject
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import com.intellij.psi.PsiClass
import org.jetbrains.kotlin.psi.JetNameReferenceExpression
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.idea.completion.isAfterDot
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
public object KotlinClassInsertHandler : BaseDeclarationInsertHandler() {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
super.handleInsert(context, item)
val file = context.getFile()
if (file is JetFile) {
if (!context.isAfterDot()) {
val psiDocumentManager = PsiDocumentManager.getInstance(context.getProject())
psiDocumentManager.commitAllDocuments()
val startOffset = context.getStartOffset()
val document = context.getDocument()
val qualifiedName = qualifiedNameToInsert(item)
// first try to resolve short name for faster handling
val token = file.findElementAt(startOffset)
val nameRef = token.getParent() as? JetNameReferenceExpression
if (nameRef != null) {
val bindingContext = nameRef.getResolutionFacade().analyze(nameRef, BodyResolveMode.PARTIAL)
val target = bindingContext[BindingContext.REFERENCE_TARGET, nameRef] as? ClassDescriptor
if (target != null && IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(target) == qualifiedName) return
}
val tempPrefix = if (nameRef != null)
" " // insert space so that any preceding spaces inserted by formatter on reference shortening are deleted
else
"$;val v:" // if we have no reference in the current context we have a more complicated prefix to get one
val tempSuffix = ".xxx" // we add "xxx" after dot because of some bugs in resolve (see KT-5145)
document.replaceString(startOffset, context.getTailOffset(), tempPrefix + qualifiedName + tempSuffix)
psiDocumentManager.commitAllDocuments()
val classNameStart = startOffset + tempPrefix.length()
val classNameEnd = classNameStart + qualifiedName.length()
val rangeMarker = document.createRangeMarker(classNameStart, classNameEnd)
val wholeRangeMarker = document.createRangeMarker(startOffset, classNameEnd + tempSuffix.length())
ShortenReferences.DEFAULT.process(file, classNameStart, classNameEnd)
psiDocumentManager.doPostponedOperationsAndUnblockDocument(document)
if (rangeMarker.isValid() && wholeRangeMarker.isValid()) {
document.deleteString(wholeRangeMarker.getStartOffset(), rangeMarker.getStartOffset())
document.deleteString(rangeMarker.getEndOffset(), wholeRangeMarker.getEndOffset())
}
}
}
}
private fun qualifiedNameToInsert(item: LookupElement): String {
val lookupObject = item.getObject()
return when (lookupObject) {
is DeclarationDescriptorLookupObject -> IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(lookupObject.descriptor as ClassDescriptor)
is PsiClass -> lookupObject.getQualifiedName()!!
else -> error("Unknown object in LookupElement with KotlinClassInsertHandler: $lookupObject")
}
}
}
@@ -0,0 +1,53 @@
/*
* 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.handlers
import com.intellij.codeInsight.completion.InsertHandler
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.LookupElement
import org.jetbrains.kotlin.lexer.JetTokens.*
public object KotlinKeywordInsertHandler : InsertHandler<LookupElement> {
private val NO_SPACE_AFTER = listOf(THIS_KEYWORD,
SUPER_KEYWORD,
FOR_KEYWORD,
NULL_KEYWORD,
TRUE_KEYWORD,
FALSE_KEYWORD,
BREAK_KEYWORD,
CONTINUE_KEYWORD,
IF_KEYWORD,
ELSE_KEYWORD,
WHILE_KEYWORD,
DO_KEYWORD,
TRY_KEYWORD,
WHEN_KEYWORD,
FILE_KEYWORD,
CATCH_KEYWORD,
FINALLY_KEYWORD,
DYNAMIC_KEYWORD).map { it.getValue() }
override fun handleInsert(context: InsertionContext, item: LookupElement) {
val keyword = item.getLookupString()
if (keyword == FILE_KEYWORD.getValue()) {
WithTailInsertHandler.colonTail().postHandleInsert(context, item)
}
else if (keyword !in NO_SPACE_AFTER) {
WithTailInsertHandler.spaceTail().postHandleInsert(context, item)
}
}
}
@@ -0,0 +1,96 @@
/*
* 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.handlers
import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.psi.PsiDocumentManager
import com.intellij.codeInsight.AutoPopupController
import com.intellij.codeInsight.lookup.Lookup
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletion
import org.jetbrains.kotlin.idea.completion.KEEP_OLD_ARGUMENT_LIST_ON_TAB_KEY
import com.intellij.openapi.util.TextRange
class WithTailInsertHandler(val tailText: String,
val spaceBefore: Boolean,
val spaceAfter: Boolean,
val overwriteText: Boolean = true) : InsertHandler<LookupElement> {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
item.handleInsert(context)
postHandleInsert(context, item)
}
fun postHandleInsert(context: InsertionContext, item: LookupElement) {
val completionChar = context.getCompletionChar()
if (completionChar == tailText.singleOrNull() || (spaceAfter && completionChar == ' ')) {
context.setAddCompletionChar(false)
}
//TODO: what if completion char is different?
val document = context.getDocument()
PsiDocumentManager.getInstance(context.getProject()).doPostponedOperationsAndUnblockDocument(document)
var tailOffset = context.getTailOffset()
if (completionChar == Lookup.REPLACE_SELECT_CHAR && item.getUserData(KEEP_OLD_ARGUMENT_LIST_ON_TAB_KEY) != null) {
val offset = context.getOffsetMap().getOffset(SmartCompletion.OLD_ARGUMENTS_REPLACEMENT_OFFSET)
if (offset != -1) tailOffset = offset
}
val moveCaret = context.getEditor().getCaretModel().getOffset() == tailOffset
if (overwriteText) {
fun isCharAt(offset: Int, c: Char) = offset < document.getTextLength() && document.getCharsSequence().charAt(offset) == c
fun isTextAt(offset: Int, text: String) = offset + text.length <= document.getTextLength() && document.getText(TextRange(offset, offset + text.length)) == text
if (spaceBefore && isCharAt(tailOffset, ' ')) {
document.deleteString(tailOffset, tailOffset + 1)
}
if (isTextAt(tailOffset, tailText)) {
document.deleteString(tailOffset, tailOffset + tailText.length)
if (spaceAfter && isCharAt(tailOffset, ' ')) {
document.deleteString(tailOffset, tailOffset + 1)
}
}
}
var textToInsert = tailText
if (spaceBefore) textToInsert = " " + textToInsert
if (spaceAfter) textToInsert += " "
document.insertString(tailOffset, textToInsert)
if (moveCaret) {
context.getEditor().getCaretModel().moveToOffset(tailOffset + textToInsert.length)
if (tailText == ",") {
AutoPopupController.getInstance(context.getProject())?.autoPopupParameterInfo(context.getEditor(), null)
}
}
}
companion object {
fun commaTail() = WithTailInsertHandler(",", spaceBefore = false, spaceAfter = true /*TODO: use code style option*/)
fun rparenthTail() = WithTailInsertHandler(")", spaceBefore = false, spaceAfter = false)
fun elseTail() = WithTailInsertHandler("else", spaceBefore = true, spaceAfter = true)
fun eqTail() = WithTailInsertHandler("=", spaceBefore = true, spaceAfter = true) /*TODO: use code style options*/
fun spaceTail() = WithTailInsertHandler(" ", spaceBefore = false, spaceAfter = false, overwriteText = false)
fun colonTail() = WithTailInsertHandler(":", spaceBefore = false, spaceAfter = true)
}
}
@@ -0,0 +1,63 @@
/*
* 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.smart
import com.intellij.codeInsight.lookup.LookupElement
import org.jetbrains.kotlin.idea.completion.ExpectedInfo
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import com.intellij.codeInsight.lookup.LookupElementBuilder
import org.jetbrains.kotlin.psi.*
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.codeInsight.completion.InsertionContext
import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler
import org.jetbrains.kotlin.types.TypeSubstitutor
object KeywordValues {
public fun addToCollection(collection: MutableCollection<LookupElement>, expectedInfos: Collection<ExpectedInfo>, expressionWithType: JetExpression) {
var skipTrueFalse = false
val whenCondition = expressionWithType.getParent() as? JetWhenConditionWithExpression
if (whenCondition != null) {
val entry = whenCondition.getParent() as JetWhenEntry
val whenExpression = entry.getParent() as JetWhenExpression
if (whenExpression.getElseExpression() == null && entry == whenExpression.getEntries().last) {
val lookupElement = LookupElementBuilder.create("else").bold().withTailText(" ->")
collection.add(object: LookupElementDecorator<LookupElement>(lookupElement) {
override fun handleInsert(context: InsertionContext) {
WithTailInsertHandler("->", spaceBefore = true, spaceAfter = true).handleInsert(context, getDelegate())
}
})
}
if (whenExpression.getSubjectExpression() == null) { // no sense in true or false entries for when with no subject
skipTrueFalse = true
}
}
if (!skipTrueFalse) {
val booleanInfoClassifier = { (info: ExpectedInfo) ->
if (info.type == KotlinBuiltIns.getInstance().getBooleanType()) ExpectedInfoClassification.matches(TypeSubstitutor.EMPTY) else ExpectedInfoClassification.notMatches
}
collection.addLookupElements(null, expectedInfos, booleanInfoClassifier, { LookupElementBuilder.create("true").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.TRUE) })
collection.addLookupElements(null, expectedInfos, booleanInfoClassifier, { LookupElementBuilder.create("false").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.FALSE) })
}
collection.addLookupElements(null,
expectedInfos,
{ info -> if (info.type.isMarkedNullable()) ExpectedInfoClassification.matches(TypeSubstitutor.EMPTY) else ExpectedInfoClassification.notMatches },
{ LookupElementBuilder.create("null").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.NULL) })
}
}
@@ -0,0 +1,61 @@
/*
* 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.smart
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.idea.completion.handlers.insertLambdaTemplate
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.idea.completion.ExpectedInfo
import org.jetbrains.kotlin.idea.completion.handlers.buildLambdaPresentation
import org.jetbrains.kotlin.idea.completion.suppressAutoInsertion
object LambdaItems {
public fun addToCollection(collection: MutableCollection<LookupElement>, functionExpectedInfos: Collection<ExpectedInfo>) {
val distinctTypes = functionExpectedInfos.map { it.type }.toSet()
val singleType = if (distinctTypes.size == 1) distinctTypes.single() else null
val singleSignatureLength = singleType?.let { KotlinBuiltIns.getParameterTypeProjectionsFromFunctionType(it).size }
val offerNoParametersLambda = singleSignatureLength == 0 || singleSignatureLength == 1
if (offerNoParametersLambda) {
val lookupElement = LookupElementBuilder.create("{...}")
.withInsertHandler(ArtificialElementInsertHandler("{ ", " }", false))
.suppressAutoInsertion()
.assignSmartCompletionPriority(SmartCompletionItemPriority.LAMBDA_NO_PARAMS)
.addTailAndNameSimilarity(functionExpectedInfos)
collection.add(lookupElement)
}
if (singleSignatureLength != 0) {
for (functionType in distinctTypes) {
val lookupString = buildLambdaPresentation(functionType)
val lookupElement = LookupElementBuilder.create(lookupString)
.withInsertHandler({ (context, lookupElement) ->
val offset = context.getStartOffset()
val placeholder = "{}"
context.getDocument().replaceString(offset, context.getTailOffset(), placeholder)
insertLambdaTemplate(context, TextRange(offset, offset + placeholder.length), functionType)
})
.suppressAutoInsertion()
.assignSmartCompletionPriority(SmartCompletionItemPriority.LAMBDA)
.addTailAndNameSimilarity(functionExpectedInfos.filter { it.type == functionType })
collection.add(lookupElement)
}
}
}
}
@@ -0,0 +1,102 @@
/*
* 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.smart
import org.jetbrains.kotlin.idea.completion.ExpectedInfo
import org.jetbrains.kotlin.psi.JetExpression
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.ui.LayeredIcon
import com.intellij.codeInsight.lookup.LookupElementBuilder
import org.jetbrains.kotlin.idea.completion.Tail
import org.jetbrains.kotlin.idea.completion.ItemPriority
import org.jetbrains.kotlin.resolve.BindingContext
import java.util.HashSet
import org.jetbrains.kotlin.resolve.scopes.JetScope
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.JetDescriptorIconProvider
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.completion.PositionalArgumentExpectedInfo
import java.util.ArrayList
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.completion.assignPriority
import com.intellij.codeInsight.lookup.Lookup
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.types.checker.JetTypeChecker
class MultipleArgumentsItemProvider(val bindingContext: BindingContext,
val smartCastTypes: (VariableDescriptor) -> Collection<JetType>) {
public fun addToCollection(collection: MutableCollection<LookupElement>,
expectedInfos: Collection<ExpectedInfo>,
context: JetExpression) {
val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, context] ?: return
val added = HashSet<String>()
for (expectedInfo in expectedInfos) {
if (expectedInfo is PositionalArgumentExpectedInfo && expectedInfo.argumentIndex == 0) {
val parameters = expectedInfo.function.getValueParameters()
if (parameters.size > 1) {
val variables = ArrayList<VariableDescriptor>()
for ((i, parameter) in parameters.withIndices()) {
val variable = variableInScope(parameter, resolutionScope) ?: break
variables.add(variable) // TODO: cannot inline variable because of KT-5890
if (i > 0 && parameters.stream().drop(i + 1).all { it.hasDefaultValue() }) { // this is the last parameter or all others have default values
val lookupElement = createParametersLookupElement(variables)
if (added.add(lookupElement.getLookupString())) { // check that we don't already have item with the same text
collection.add(lookupElement)
}
}
}
}
}
}
}
private fun createParametersLookupElement(variables: List<VariableDescriptor>): LookupElement {
val compoundIcon = LayeredIcon(2)
val firstIcon = JetDescriptorIconProvider.getIcon(variables.first(), null, 0)
val lastIcon = JetDescriptorIconProvider.getIcon(variables.last(), null, 0)
compoundIcon.setIcon(lastIcon, 0, 2 * firstIcon.getIconWidth() / 5, 0)
compoundIcon.setIcon(firstIcon, 1, 0, 0)
return LookupElementBuilder
.create(variables.map { IdeDescriptorRenderers.SOURCE_CODE.renderName(it.getName()) }.joinToString(", "))
.withInsertHandler { (context, lookupElement) ->
if (context.getCompletionChar() == Lookup.REPLACE_SELECT_CHAR) {
val offset = context.getOffsetMap().getOffset(SmartCompletion.MULTIPLE_ARGUMENTS_REPLACEMENT_OFFSET)
if (offset != -1) {
context.getDocument().deleteString(context.getTailOffset(), offset)
}
}
}
.withIcon(compoundIcon)
.addTail(Tail.RPARENTH) //TODO: support square brackets
.assignPriority(ItemPriority.MULTIPLE_ARGUMENTS_ITEM)
}
private fun variableInScope(parameter: ValueParameterDescriptor, scope: JetScope): VariableDescriptor? {
val name = parameter.getName()
//TODO: there can be more than one property with such name in scope and we should be able to select one (but we need API for this)
val variable = scope.getLocalVariable(name) ?: scope.getProperties(name).singleOrNull() ?: return null
return if (smartCastTypes(variable).any { JetTypeChecker.DEFAULT.isSubtypeOf(it, parameter.getType()) })
variable
else
null
}
}
@@ -0,0 +1,59 @@
/*
* 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.smart
import com.intellij.openapi.util.Key
import com.intellij.codeInsight.lookup.LookupElementWeigher
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.WeighingContext
import org.jetbrains.kotlin.idea.completion.ExpectedInfo
import com.intellij.psi.codeStyle.NameUtil
val NAME_SIMILARITY_KEY = Key<Int>("NAME_SIMILARITY_KEY")
object NameSimilarityWeigher : LookupElementWeigher("kotlin.nameSimilarity") {
override fun weigh(element: LookupElement, context: WeighingContext)
= -(element.getUserData(NAME_SIMILARITY_KEY) ?: 0)
}
fun calcNameSimilarity(name: String, expectedInfos: Collection<ExpectedInfo>): Int {
return expectedInfos
.map { it.name }
.filterNotNull()
.map { calcNameSimilarity(name, it) }
.max() ?: 0
}
private fun calcNameSimilarity(name: String, expectedName: String): Int {
val words1 = NameUtil.nameToWordsLowerCase(name)
val words2 = NameUtil.nameToWordsLowerCase(expectedName)
val matchedWords = words1.toSet().intersect(words2)
if (matchedWords.isEmpty()) return 0
fun isNonNumber(word: String) = !word[0].isDigit()
val nonNumberWords1 = words1.filter(::isNonNumber)
val nonNumberWords2 = words2.filter(::isNonNumber)
// count number of words matched at the end (but ignore number words - they are less important)
val minWords = Math.min(nonNumberWords1.size, nonNumberWords2.size)
val matchedTailLength = (0..minWords-1).firstOrNull {
i -> nonNumberWords1[nonNumberWords1.size - i - 1] != nonNumberWords2[nonNumberWords2.size - i - 1]
} ?: minWords
return matchedWords.size * 1000 + matchedTailLength
}
@@ -0,0 +1,487 @@
/*
* 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.smart
import com.intellij.codeInsight.completion.InsertHandler
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.completion.OffsetKey
import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.codeInsight.lookup.Lookup
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.codeInsight.lookup.LookupElementPresentation
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.completion.*
import org.jetbrains.kotlin.idea.core.IterableTypesDetector
import org.jetbrains.kotlin.idea.core.SmartCastCalculator
import org.jetbrains.kotlin.idea.util.*
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import java.util.ArrayList
import java.util.HashSet
trait InheritanceItemsSearcher {
fun search(nameFilter: (String) -> Boolean, consumer: (LookupElement) -> Unit)
}
class SmartCompletion(
val expression: JetExpression,
val resolutionFacade: ResolutionFacade,
val moduleDescriptor: ModuleDescriptor,
val bindingContext: BindingContext,
val visibilityFilter: (DeclarationDescriptor) -> Boolean,
val inDescriptor: DeclarationDescriptor?,
val prefixMatcher: PrefixMatcher,
val inheritorSearchScope: GlobalSearchScope,
val toFromOriginalFileMapper: ToFromOriginalFileMapper,
val lookupElementFactory: LookupElementFactory
) {
private val project = expression.getProject()
public class Result(
val declarationFilter: ((DeclarationDescriptor) -> Collection<LookupElement>)?,
val additionalItems: Collection<LookupElement>,
val inheritanceSearcher: InheritanceItemsSearcher?)
public fun execute(): Result? {
fun postProcess(item: LookupElement): LookupElement {
return if (item.getUserData(KEEP_OLD_ARGUMENT_LIST_ON_TAB_KEY) == null) {
object : LookupElementDecorator<LookupElement>(item) {
override fun handleInsert(context: InsertionContext) {
if (context.getCompletionChar() == Lookup.REPLACE_SELECT_CHAR) {
val offset = context.getOffsetMap().getOffset(OLD_ARGUMENTS_REPLACEMENT_OFFSET)
if (offset != -1) {
context.getDocument().deleteString(context.getTailOffset(), offset)
}
}
super.handleInsert(context)
}
}
}
else {
item
}
}
val result = executeInternal() ?: return null
// TODO: code could be more simple, see KT-5726
val additionalItems = result.additionalItems.map(::postProcess)
val inheritanceSearcher = result.inheritanceSearcher?.let {
object : InheritanceItemsSearcher {
override fun search(nameFilter: (String) -> Boolean, consumer: (LookupElement) -> Unit) {
it.search(nameFilter, { consumer(postProcess(it)) })
}
}
}
val filter = result.declarationFilter
return if (filter != null)
Result({ filter(it).map(::postProcess) }, additionalItems, inheritanceSearcher)
else
Result(null, additionalItems, inheritanceSearcher)
}
private fun executeInternal(): Result? {
val asTypePositionResult = buildForAsTypePosition()
if (asTypePositionResult != null) return asTypePositionResult
val receiver = if (expression is JetSimpleNameExpression) expression.getReceiverExpression() else null
val expressionWithType = if (receiver != null) {
expression.getParent() as? JetExpression ?: return null
}
else {
expression
}
val loopRangePositionResult = buildForLoopRangePosition(expressionWithType, receiver)
if (loopRangePositionResult != null) return loopRangePositionResult
val inOperatorArgumentResult = buildForInOperatorArgument(expressionWithType, receiver)
if (inOperatorArgumentResult != null) return inOperatorArgumentResult
val allExpectedInfos = calcExpectedInfos(expressionWithType) ?: return null
val filteredExpectedInfos = allExpectedInfos.filter { !it.type.isError() }
if (filteredExpectedInfos.isEmpty()) return null
// if we complete argument of == or !=, make types in expected info's nullable to allow nullable items too
val expectedInfos = if ((expressionWithType.getParent() as? JetBinaryExpression)?.getOperationToken() in COMPARISON_TOKENS)
filteredExpectedInfos.map { ExpectedInfo(it.type.makeNullable(), it.name, it.tail) }
else
filteredExpectedInfos
val smartCastTypes: (VariableDescriptor) -> Collection<JetType> = SmartCastCalculator(
bindingContext, moduleDescriptor).calculate(expressionWithType, receiver)
val itemsToSkip = calcItemsToSkip(expressionWithType)
val functionExpectedInfos = expectedInfos.filter { KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(it.type) }
fun filterDeclaration(descriptor: DeclarationDescriptor): Collection<LookupElement> {
if (descriptor in itemsToSkip) return listOf()
val result = ArrayList<LookupElement>()
val types = descriptor.fuzzyTypes(smartCastTypes)
val infoClassifier = { (expectedInfo: ExpectedInfo) -> types.classifyExpectedInfo(expectedInfo) }
result.addLookupElements(descriptor, expectedInfos, infoClassifier) { descriptor ->
lookupElementFactory.createLookupElement(descriptor, resolutionFacade, bindingContext, true)
}
if (descriptor is PropertyDescriptor) {
result.addLookupElements(descriptor, expectedInfos, infoClassifier) { descriptor ->
lookupElementFactory.createBackingFieldLookupElement(descriptor, inDescriptor, resolutionFacade)
}
}
if (receiver == null) {
toFunctionReferenceLookupElement(descriptor, functionExpectedInfos)?.let { result.add(it) }
}
return result
}
val additionalItems = ArrayList<LookupElement>()
val inheritanceSearchers = ArrayList<InheritanceItemsSearcher>()
if (receiver == null) {
TypeInstantiationItems(resolutionFacade, moduleDescriptor, bindingContext, visibilityFilter, toFromOriginalFileMapper, inheritorSearchScope, lookupElementFactory)
.addTo(additionalItems, inheritanceSearchers, expectedInfos)
if (expression is JetSimpleNameExpression) {
StaticMembers(bindingContext, resolutionFacade, lookupElementFactory).addToCollection(additionalItems, expectedInfos, expression, itemsToSkip)
}
additionalItems.addThisItems(expression, expectedInfos)
LambdaItems.addToCollection(additionalItems, functionExpectedInfos)
KeywordValues.addToCollection(additionalItems, filteredExpectedInfos/* use filteredExpectedInfos to not include null after == */, expression)
MultipleArgumentsItemProvider(bindingContext, smartCastTypes).addToCollection(additionalItems, expectedInfos, expression)
}
val inheritanceSearcher = if (inheritanceSearchers.isNotEmpty())
object : InheritanceItemsSearcher {
override fun search(nameFilter: (String) -> Boolean, consumer: (LookupElement) -> Unit) {
inheritanceSearchers.forEach { it.search(nameFilter, consumer) }
}
}
else
null
return Result(::filterDeclaration, additionalItems, inheritanceSearcher)
}
private fun MutableCollection<LookupElement>.addThisItems(place: JetExpression, expectedInfos: Collection<ExpectedInfo>) {
if (shouldCompleteThisItems(prefixMatcher)) {
val items = thisExpressionItems(bindingContext, place, prefixMatcher.getPrefix())
for ((factory, type) in items) {
val classifier = { (expectedInfo: ExpectedInfo) -> type.classifyExpectedInfo(expectedInfo) }
addLookupElements(null, expectedInfos, classifier) {
factory().assignSmartCompletionPriority(SmartCompletionItemPriority.THIS)
}
}
}
}
private fun DeclarationDescriptor.fuzzyTypes(smartCastTypes: (VariableDescriptor) -> Collection<JetType>): Collection<FuzzyType> {
if (this is CallableDescriptor) {
var returnType = fuzzyReturnType() ?: return listOf()
//TODO: maybe we should include them on the second press?
if (shouldSkipDeclarationsOfType(returnType)) return listOf()
if (this is VariableDescriptor) {
return smartCastTypes(this).map { FuzzyType(it, listOf()) }
}
else {
return listOf(fuzzyReturnType()!!)
}
}
else if (this is ClassDescriptor && getKind().isSingleton()) {
return listOf(FuzzyType(getDefaultType(), listOf()))
}
else {
return listOf()
}
}
// skip declarations of type Nothing or of generic parameter type which has no real bounds
private fun shouldSkipDeclarationsOfType(type: FuzzyType): Boolean {
if (KotlinBuiltIns.isNothing(type.type)) return true
if (type.freeParameters.isEmpty()) return false
val typeParameter = type.type.getConstructor().getDeclarationDescriptor() as? TypeParameterDescriptor ?: return false
if (!type.freeParameters.contains(typeParameter)) return false
return KotlinBuiltIns.isAnyOrNullableAny(typeParameter.getUpperBoundsAsType())
//TODO: check for companion object constraint when they are supported
}
private fun calcExpectedInfos(expression: JetExpression): Collection<ExpectedInfo>? {
// if our expression is initializer of implicitly typed variable - take type of variable from original file (+ the same for function)
val declaration = implicitlyTypedDeclarationFromInitializer(expression)
if (declaration != null) {
val originalDeclaration = toFromOriginalFileMapper.toOriginalFile(declaration)
if (originalDeclaration != null) {
val originalDescriptor = originalDeclaration.resolveToDescriptor() as? CallableDescriptor
val returnType = originalDescriptor?.getReturnType()
return if (returnType != null) listOf(ExpectedInfo(returnType, declaration.getName(), null)) else null
}
}
return ExpectedInfos(bindingContext, resolutionFacade, moduleDescriptor, true).calculate(expression)
}
private fun implicitlyTypedDeclarationFromInitializer(expression: JetExpression): JetDeclaration? {
val parent = expression.getParent()
when (parent) {
is JetVariableDeclaration -> if (expression == parent.getInitializer() && parent.getTypeReference() == null) return parent
is JetNamedFunction -> if (expression == parent.getInitializer() && parent.getTypeReference() == null) return parent
}
return null
}
private fun calcItemsToSkip(expression: JetExpression): Set<DeclarationDescriptor> {
val parent = expression.getParent()
when(parent) {
is JetProperty -> {
//TODO: this can be filtered out by ordinary completion
if (expression == parent.getInitializer()) {
return bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, parent].toSet()
}
}
is JetBinaryExpression -> {
if (parent.getRight() == expression) {
val operationToken = parent.getOperationToken()
if (operationToken == JetTokens.EQ || operationToken in COMPARISON_TOKENS) {
val left = parent.getLeft()
if (left is JetReferenceExpression) {
return bindingContext[BindingContext.REFERENCE_TARGET, left].toSet()
}
}
}
}
is JetWhenConditionWithExpression -> {
val entry = parent.getParent() as JetWhenEntry
val whenExpression = entry.getParent() as JetWhenExpression
val subject = whenExpression.getSubjectExpression() ?: return setOf()
val itemsToSkip = HashSet<DeclarationDescriptor>()
if (subject is JetSimpleNameExpression) {
val variable = bindingContext[BindingContext.REFERENCE_TARGET, subject] as? VariableDescriptor
if (variable != null) {
itemsToSkip.add(variable)
}
}
val subjectType = bindingContext[BindingContext.EXPRESSION_TYPE, subject] ?: return setOf()
val classDescriptor = TypeUtils.getClassDescriptor(subjectType)
if (classDescriptor != null && DescriptorUtils.isEnumClass(classDescriptor)) {
val conditions = whenExpression.getEntries()
.flatMap { it.getConditions().toList() }
.filterIsInstance<JetWhenConditionWithExpression>()
for (condition in conditions) {
val selectorExpr = (condition.getExpression() as? JetDotQualifiedExpression)
?.getSelectorExpression() as? JetReferenceExpression ?: continue
val target = bindingContext[BindingContext.REFERENCE_TARGET, selectorExpr] as? ClassDescriptor ?: continue
if (DescriptorUtils.isEnumEntry(target)) {
itemsToSkip.add(target)
}
}
}
return itemsToSkip
}
}
return setOf()
}
private fun toFunctionReferenceLookupElement(descriptor: DeclarationDescriptor,
functionExpectedInfos: Collection<ExpectedInfo>): LookupElement? {
if (functionExpectedInfos.isEmpty()) return null
fun toLookupElement(descriptor: FunctionDescriptor): LookupElement? {
val functionType = functionType(descriptor) ?: return null
val matchedExpectedInfos = functionExpectedInfos.filter { functionType.isSubtypeOf(it.type) }
if (matchedExpectedInfos.isEmpty()) return null
var lookupElement = lookupElementFactory.createLookupElement(descriptor, resolutionFacade, bindingContext, true)
val text = "::" + (if (descriptor is ConstructorDescriptor) descriptor.getContainingDeclaration().getName() else descriptor.getName())
lookupElement = object: LookupElementDecorator<LookupElement>(lookupElement) {
override fun getLookupString() = text
override fun getAllLookupStrings() = setOf(text)
override fun renderElement(presentation: LookupElementPresentation) {
super.renderElement(presentation)
presentation.setItemText(text)
presentation.clearTail()
presentation.setTypeText(null)
}
override fun handleInsert(context: InsertionContext) {
}
}
return lookupElement
.assignSmartCompletionPriority(SmartCompletionItemPriority.FUNCTION_REFERENCE)
.addTailAndNameSimilarity(matchedExpectedInfos)
}
if (descriptor is SimpleFunctionDescriptor) {
return toLookupElement(descriptor)
}
else if (descriptor is ClassDescriptor && descriptor.getModality() != Modality.ABSTRACT) {
val constructors = descriptor.getConstructors().filter(visibilityFilter)
if (constructors.size == 1) {
//TODO: this code is to be changed if overloads to start work after ::
return toLookupElement(constructors.single())
}
}
return null
}
private fun buildForAsTypePosition(): Result? {
val binaryExpression = ((expression.getParent() as? JetUserType)
?.getParent() as? JetTypeReference)
?.getParent() as? JetBinaryExpressionWithTypeRHS
?: return null
val elementType = binaryExpression.getOperationReference().getReferencedNameElementType()
if (elementType != JetTokens.AS_KEYWORD && elementType != JetTokens.AS_SAFE) return null
val expectedInfos = calcExpectedInfos(binaryExpression) ?: return null
val expectedInfosGrouped: Map<JetType, List<ExpectedInfo>> = expectedInfos.groupBy { it.type.makeNotNullable() }
val items = ArrayList<LookupElement>()
for ((jetType, infos) in expectedInfosGrouped) {
val lookupElement = lookupElementForType(jetType) ?: continue
items.add(lookupElement.addTailAndNameSimilarity(infos))
}
return Result(null, items, null)
}
private fun buildForLoopRangePosition(expressionWithType: JetExpression, receiver: JetExpression?): Result? {
val forExpression = (expressionWithType.getParent() as? JetContainerNode)
?.getParent() as? JetForExpression ?: return null
if (expressionWithType != forExpression.getLoopRange()) return null
val loopVar = forExpression.getLoopParameter()
val loopVarType = if (loopVar != null && loopVar.getTypeReference() != null) {
val type = (resolutionFacade.resolveToDescriptor(loopVar) as VariableDescriptor).getType()
if (type.isError()) null else type
}
else {
null
}
val scope = bindingContext.get(BindingContext.RESOLUTION_SCOPE, expressionWithType)
val iterableDetector = IterableTypesDetector(project, moduleDescriptor, scope, loopVarType)
return buildResultByTypeFilter(expressionWithType, receiver, Tail.RPARENTH) { iterableDetector.isIterable(it) }
}
private fun buildForInOperatorArgument(expressionWithType: JetExpression, receiver: JetExpression?): Result? {
val binaryExpression = expressionWithType.getParent() as? JetBinaryExpression ?: return null
val operationToken = binaryExpression.getOperationToken()
if (operationToken != JetTokens.IN_KEYWORD && operationToken != JetTokens.NOT_IN || expressionWithType != binaryExpression.getRight()) return null
val leftOperandType = bindingContext.get(BindingContext.EXPRESSION_TYPE, binaryExpression.getLeft()) ?: return null
val scope = bindingContext.get(BindingContext.RESOLUTION_SCOPE, expressionWithType)
val detector = TypesWithContainsDetector(scope, leftOperandType, project, moduleDescriptor)
return buildResultByTypeFilter(expressionWithType, receiver, null) { detector.hasContains(it) }
}
private fun buildResultByTypeFilter(
position: JetExpression,
receiver: JetExpression?,
tail: Tail?,
typeFilter: (FuzzyType) -> Boolean
): Result {
val smartCastTypes: (VariableDescriptor) -> Collection<JetType> = SmartCastCalculator(
bindingContext, moduleDescriptor).calculate(position, receiver)
fun filterDeclaration(descriptor: DeclarationDescriptor): Collection<LookupElement> {
val types = descriptor.fuzzyTypes(smartCastTypes)
fun createLookupElement(): LookupElement {
return lookupElementFactory.createLookupElement(descriptor, resolutionFacade, bindingContext, true)
}
if (types.any { typeFilter(it) }) {
return listOf(createLookupElement().addTail(tail))
}
if (types.any { it.nullability() == TypeNullability.NULLABLE && typeFilter(it.makeNotNullable()) }) {
return lookupElementsForNullable(::createLookupElement).map { it.addTail(tail) }
}
return listOf()
}
return Result(::filterDeclaration, listOf(), null)
}
private fun lookupElementForType(jetType: JetType): LookupElement? {
if (jetType.isError()) return null
val classifier = jetType.getConstructor().getDeclarationDescriptor() ?: return null
val lookupElement = lookupElementFactory.createLookupElement(classifier, resolutionFacade, bindingContext, false)
val lookupString = lookupElement.getLookupString()
val typeArgs = jetType.getArguments()
var itemText = lookupString + DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderTypeArguments(typeArgs)
val typeText = DescriptorUtils.getFqName(classifier).toString() + IdeDescriptorRenderers.SOURCE_CODE.renderTypeArguments(typeArgs)
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")
private val COMPARISON_TOKENS = setOf(JetTokens.EQEQ, JetTokens.EXCLEQ, JetTokens.EQEQEQ, JetTokens.EXCLEQEQEQ)
}
}
@@ -0,0 +1,144 @@
/*
* 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.smart
import com.intellij.codeInsight.lookup.LookupElement
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.resolve.DescriptorUtils
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.codeInsight.lookup.LookupElementPresentation
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import com.intellij.codeInsight.completion.InsertionContext
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.completion.LookupElementFactory
import org.jetbrains.kotlin.types.TypeSubstitutor
import org.jetbrains.kotlin.idea.util.fuzzyReturnType
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
import org.jetbrains.kotlin.idea.completion.ExpectedInfo
import org.jetbrains.kotlin.idea.core.isVisible
// adds java static members, enum members and members from companion object
class StaticMembers(
val bindingContext: BindingContext,
val resolutionFacade: ResolutionFacade,
val lookupElementFactory: LookupElementFactory
) {
public fun addToCollection(collection: MutableCollection<LookupElement>,
expectedInfos: Collection<ExpectedInfo>,
context: JetSimpleNameExpression,
enumEntriesToSkip: Set<DeclarationDescriptor>) {
val expectedInfosByClass = expectedInfos.groupBy { TypeUtils.getClassDescriptor(it.type) }
for ((classDescriptor, expectedInfosForClass) in expectedInfosByClass) {
if (classDescriptor != null && !classDescriptor.getName().isSpecial()) {
addToCollection(collection, classDescriptor, expectedInfosForClass, context, enumEntriesToSkip)
}
}
}
private fun addToCollection(
collection: MutableCollection<LookupElement>,
classDescriptor: ClassDescriptor,
expectedInfos: Collection<ExpectedInfo>,
context: JetSimpleNameExpression,
enumEntriesToSkip: Set<DeclarationDescriptor>) {
val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, context] ?: return
fun processMember(descriptor: DeclarationDescriptor) {
if (descriptor is DeclarationDescriptorWithVisibility && !descriptor.isVisible(scope.getContainingDeclaration(), bindingContext, context)) return
val classifier: (ExpectedInfo) -> ExpectedInfoClassification
if (descriptor is CallableDescriptor) {
val returnType = descriptor.fuzzyReturnType() ?: return
classifier = { expectedInfo -> returnType.classifyExpectedInfo(expectedInfo) }
}
else if (DescriptorUtils.isEnumEntry(descriptor) && !enumEntriesToSkip.contains(descriptor)) {
classifier = { ExpectedInfoClassification.matches(TypeSubstitutor.EMPTY) } /* we do not need to check type of enum entry because it's taken from proper enum */
}
else {
return
}
collection.addLookupElements(descriptor, expectedInfos, classifier) {
descriptor -> createLookupElement(descriptor, classDescriptor)
}
}
classDescriptor.getStaticScope().getAllDescriptors().forEach(::processMember)
val companionObject = classDescriptor.getCompanionObjectDescriptor()
if (companionObject != null) {
companionObject.getDefaultType().getMemberScope().getAllDescriptors()
.filter { !it.isExtension }
.forEach(::processMember)
}
var members = classDescriptor.getDefaultType().getMemberScope().getAllDescriptors()
if (classDescriptor.getKind() != ClassKind.ENUM_CLASS) {
members = members.filter { DescriptorUtils.isNonCompanionObject(it) }
}
members.forEach(::processMember)
}
private fun createLookupElement(memberDescriptor: DeclarationDescriptor, classDescriptor: ClassDescriptor): LookupElement {
val lookupElement = lookupElementFactory.createLookupElement(memberDescriptor, resolutionFacade, bindingContext, false)
val qualifierPresentation = classDescriptor.getName().asString()
val qualifierText = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(classDescriptor)
return object: LookupElementDecorator<LookupElement>(lookupElement) {
override fun getAllLookupStrings(): Set<String> {
return setOf(lookupElement.getLookupString(), 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) {
var text = qualifierText + "." + IdeDescriptorRenderers.SOURCE_CODE.renderName(memberDescriptor.getName())
context.getDocument().replaceString(context.getStartOffset(), context.getTailOffset(), text)
context.setTailOffset(context.getStartOffset() + text.length)
if (memberDescriptor is FunctionDescriptor) {
getDelegate().handleInsert(context)
}
shortenReferences(context, context.getStartOffset(), context.getTailOffset())
}
}.assignSmartCompletionPriority(SmartCompletionItemPriority.STATIC_MEMBER)
}
}
@@ -0,0 +1,296 @@
/*
* 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.smart
import com.intellij.codeInsight.completion.InsertHandler
import com.intellij.codeInsight.completion.InsertionContext
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.search.GlobalSearchScope
import com.intellij.psi.search.searches.ClassInheritorsSearch
import org.jetbrains.kotlin.asJava.LightClassUtil
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.completion.*
import org.jetbrains.kotlin.idea.completion.handlers.CaretPosition
import org.jetbrains.kotlin.idea.completion.handlers.KotlinFunctionInsertHandler
import org.jetbrains.kotlin.idea.core.codeInsight.ImplementMethodsHandler
import org.jetbrains.kotlin.idea.core.psiClassToDescriptor
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.makeNotNullable
import org.jetbrains.kotlin.load.java.descriptors.SamConstructorDescriptor
import org.jetbrains.kotlin.psi.JetClassOrObject
import org.jetbrains.kotlin.psi.JetDeclaration
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.PossiblyBareType
import org.jetbrains.kotlin.resolve.descriptorUtil.resolveTopLevelClass
import org.jetbrains.kotlin.resolve.jvm.types.KotlinToJavaTypesMap
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import org.jetbrains.kotlin.utils.addIfNotNull
class TypeInstantiationItems(
val resolutionFacade: ResolutionFacade,
val moduleDescriptor: ModuleDescriptor,
val bindingContext: BindingContext,
val visibilityFilter: (DeclarationDescriptor) -> Boolean,
val toFromOriginalFileMapper: ToFromOriginalFileMapper,
val inheritorSearchScope: GlobalSearchScope,
val lookupElementFactory: LookupElementFactory
) {
public fun addTo(
items: MutableCollection<LookupElement>,
inheritanceSearchers: MutableCollection<InheritanceItemsSearcher>,
expectedInfos: Collection<ExpectedInfo>
) {
val expectedInfosGrouped: Map<JetType, List<ExpectedInfo>> = expectedInfos.groupBy { it.type.makeNotNullable() }
for ((type, infos) in expectedInfosGrouped) {
val tail = mergeTails(infos.map { it.tail })
addTo(items, inheritanceSearchers, type, tail)
}
}
private fun addTo(
items: MutableCollection<LookupElement>,
inheritanceSearchers: MutableCollection<InheritanceItemsSearcher>,
type: JetType,
tail: Tail?
) {
if (KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(type)) return // do not show "object: ..." for function types
val classifier = type.getConstructor().getDeclarationDescriptor()
if (classifier !is ClassDescriptor) return
addSamConstructorItem(items, classifier, tail)
val typeArgs = type.getArguments()
items.addIfNotNull(createTypeInstantiationItem(classifier, typeArgs, tail))
if (!KotlinBuiltIns.isAny(classifier)) { // do not search inheritors of Any
inheritanceSearchers.addInheritorSearcher(classifier, classifier, typeArgs, tail)
val javaAnalogFqName = KotlinToJavaTypesMap.getInstance().getKotlinToJavaFqName(DescriptorUtils.getFqNameSafe(classifier))
if (javaAnalogFqName != null) {
val javaAnalog = moduleDescriptor.resolveTopLevelClass(javaAnalogFqName)
if (javaAnalog != null) {
inheritanceSearchers.addInheritorSearcher(javaAnalog, classifier, typeArgs, tail)
}
}
}
}
private fun MutableCollection<InheritanceItemsSearcher>.addInheritorSearcher(
descriptor: ClassDescriptor, kotlinClassDescriptor: ClassDescriptor, typeArgs: List<TypeProjection>, tail: Tail?
) {
val _declaration = DescriptorToSourceUtils.descriptorToDeclaration(descriptor) ?: return
val declaration = if (_declaration is JetDeclaration)
toFromOriginalFileMapper.toOriginalFile(_declaration) ?: return
else
_declaration
val psiClass: PsiClass = when (declaration) {
is PsiClass -> declaration
is JetClassOrObject -> LightClassUtil.getPsiClass(declaration) ?: return
else -> return
}
add(InheritanceSearcher(psiClass, kotlinClassDescriptor, typeArgs, tail))
}
private fun createTypeInstantiationItem(
classifier: ClassDescriptor,
typeArgs: List<TypeProjection>,
tail: Tail?
): LookupElement? {
var lookupElement = lookupElementFactory.createLookupElement(classifier, resolutionFacade, bindingContext, false)
if (DescriptorUtils.isNonCompanionObject(classifier)) {
return lookupElement.addTail(tail)
}
// not all inner classes can be instantiated and we handle them via constructors returned by ReferenceVariantsHelper
if (classifier.isInner()) return null
val isAbstract = classifier.getModality() == Modality.ABSTRACT
val allConstructors = classifier.getConstructors()
val visibleConstructors = allConstructors.filter {
if (isAbstract)
visibilityFilter(it) || it.getVisibility() == Visibilities.PROTECTED
else
visibilityFilter(it)
}
if (allConstructors.isNotEmpty() && visibleConstructors.isEmpty()) return null
var lookupString = lookupElement.getLookupString()
var allLookupStrings = setOf(lookupString)
// drop "in" and "out" from type arguments - they cannot be used in constructor call
val typeArgsToUse = typeArgs.map { TypeProjectionImpl(Variance.INVARIANT, it.getType()) }
var itemText = lookupString + DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderTypeArguments(typeArgsToUse)
var signatureText: String? = null
val insertHandler: InsertHandler<LookupElement>
val typeText = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(classifier) + IdeDescriptorRenderers.SOURCE_CODE.renderTypeArguments(typeArgsToUse)
if (isAbstract) {
val constructorParenthesis = if (classifier.getKind() != ClassKind.TRAIT) "()" else ""
itemText += constructorParenthesis
itemText = "object: " + itemText + "{...}"
lookupString = "object"
allLookupStrings = setOf(lookupString, lookupElement.getLookupString())
insertHandler = InsertHandler<LookupElement> {(context, item) ->
val editor = context.getEditor()
val startOffset = context.getStartOffset()
val text = "object: $typeText$constructorParenthesis {}"
editor.getDocument().replaceString(startOffset, context.getTailOffset(), text)
editor.getCaretModel().moveToOffset(startOffset + text.length() - 1)
shortenReferences(context, startOffset, startOffset + text.length())
ImplementMethodsHandler().invoke(context.getProject(), editor, context.getFile(), true)
}
lookupElement = lookupElement.suppressAutoInsertion()
lookupElement = lookupElement.assignSmartCompletionPriority(SmartCompletionItemPriority.ANONYMOUS_OBJECT)
}
else {
//TODO: when constructor has one parameter of lambda type with more than one parameter, generate special additional item
signatureText = when (visibleConstructors.size()) {
0 -> "()"
1 -> DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderFunctionParameters(visibleConstructors.single())
else -> "(...)"
}
val baseInsertHandler = when (visibleConstructors.size()) {
0 -> KotlinFunctionInsertHandler.NO_PARAMETERS_HANDLER
1 -> LookupElementFactory.getDefaultInsertHandler(visibleConstructors.single()) as KotlinFunctionInsertHandler
else -> KotlinFunctionInsertHandler.WITH_PARAMETERS_HANDLER
}
insertHandler = object : InsertHandler<LookupElement> {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
context.getDocument().replaceString(context.getStartOffset(), context.getTailOffset(), typeText)
context.setTailOffset(context.getStartOffset() + typeText.length())
baseInsertHandler.handleInsert(context, item)
shortenReferences(context, context.getStartOffset(), context.getTailOffset())
}
}
if (baseInsertHandler.caretPosition == CaretPosition.IN_BRACKETS) {
lookupElement = lookupElement.keepOldArgumentListOnTab()
}
if (baseInsertHandler.lambdaInfo != null) {
lookupElement.putUserData(KotlinCompletionCharFilter.ACCEPT_OPENING_BRACE, true)
}
lookupElement = lookupElement.assignSmartCompletionPriority(SmartCompletionItemPriority.INSTANTIATION)
}
//TODO: cannot use lookupElement from context due to KT-6344
class InstantiationLookupElement(lookupElement: LookupElement) : LookupElementDecorator<LookupElement>(lookupElement) {
override fun getLookupString() = lookupString
override fun getAllLookupStrings() = allLookupStrings
override fun renderElement(presentation: LookupElementPresentation) {
getDelegate().renderElement(presentation)
presentation.setItemText(itemText)
presentation.clearTail()
if (signatureText != null) {
presentation.appendTailText(signatureText!!, false)
}
presentation.appendTailText(" (" + DescriptorUtils.getFqName(classifier.getContainingDeclaration()) + ")", true)
}
override fun handleInsert(context: InsertionContext) {
insertHandler.handleInsert(context, getDelegate())
}
override fun equals(other: Any?): Boolean {
if (other === this) return true
if (other !is InstantiationLookupElement) return false
if (getLookupString() != other.getLookupString()) return false
val presentation1 = LookupElementPresentation()
val presentation2 = LookupElementPresentation()
renderElement(presentation1)
other.renderElement(presentation2)
return presentation1.getItemText() == presentation2.getItemText() && presentation1.getTailText() == presentation2.getTailText()
}
}
return InstantiationLookupElement(lookupElement).addTail(tail)
}
private fun addSamConstructorItem(collection: MutableCollection<LookupElement>, `class`: ClassDescriptor, tail: Tail?) {
if (`class`.getKind() == ClassKind.TRAIT) {
val container = `class`.getContainingDeclaration()
val scope = when (container) {
is PackageFragmentDescriptor -> container.getMemberScope()
is ClassDescriptor -> container.getStaticScope()
else -> return
}
val samConstructor = scope.getFunctions(`class`.getName())
.filterIsInstance<SamConstructorDescriptor>()
.singleOrNull() ?: return
val lookupElement = lookupElementFactory.createLookupElement(samConstructor, resolutionFacade, bindingContext, false)
.assignSmartCompletionPriority(SmartCompletionItemPriority.INSTANTIATION)
.addTail(tail)
collection.add(lookupElement)
}
}
private inner class InheritanceSearcher(
val psiClass: PsiClass,
val classDescriptor: ClassDescriptor,
val typeArgs: List<TypeProjection>,
val tail: Tail?) : InheritanceItemsSearcher {
private val typeConstructor = classDescriptor.getTypeConstructor()
private val baseHasTypeArgs = typeConstructor.getParameters().isNotEmpty()
private val expectedType = JetTypeImpl(Annotations.EMPTY, typeConstructor, false, typeArgs, classDescriptor.getMemberScope(typeArgs))
override fun search(nameFilter: (String) -> Boolean, consumer: (LookupElement) -> Unit) {
val parameters = ClassInheritorsSearch.SearchParameters(psiClass, inheritorSearchScope, true, true, false, nameFilter)
for (inheritor in ClassInheritorsSearch.search(parameters)) {
val descriptor = resolutionFacade.psiClassToDescriptor(
inheritor,
{ toFromOriginalFileMapper.toSyntheticFile(it) as JetClassOrObject? }) as? ClassDescriptor ?: continue
if (!visibilityFilter(descriptor)) continue
val hasTypeArgs = descriptor.getTypeConstructor().getParameters().isNotEmpty()
val resultingType = if (hasTypeArgs) {
val reconstructionResult = PossiblyBareType.bare(descriptor.getTypeConstructor(), false).reconstruct(expectedType)
reconstructionResult.getResultingType() ?: continue
}
else {
descriptor.getDefaultType()
}
// check if derived type matches type arguments for base
if (baseHasTypeArgs && !resultingType.isSubtypeOf(expectedType)) continue
val lookupElement = createTypeInstantiationItem(descriptor, resultingType.getArguments(), tail) ?: continue
consumer(lookupElement.assignSmartCompletionPriority(SmartCompletionItemPriority.INHERITOR_INSTANTIATION))
}
}
}
}
@@ -0,0 +1,65 @@
/*
* 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.smart
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.resolve.scopes.JetScope
import java.util.HashMap
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.idea.util.nullability
import org.jetbrains.kotlin.idea.util.TypeNullability
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.completion.HeuristicSignatures
class TypesWithContainsDetector(
private val scope: JetScope,
private val argumentType: JetType,
private val project: Project,
private val moduleDescriptor: ModuleDescriptor
) {
private val cache = HashMap<FuzzyType, Boolean>()
private val containsName = Name.identifier("contains")
private val booleanType = KotlinBuiltIns.getInstance().getBooleanType()
private val typesWithExtensionContains: Collection<JetType> = scope.getFunctions(containsName)
.filter { it.getExtensionReceiverParameter() != null && isGoodContainsFunction(it, listOf()) }
.map { it.getExtensionReceiverParameter()!!.getType() }
public fun hasContains(type: FuzzyType): Boolean {
return cache.getOrPut(type, { hasContainsNoCache(type) })
}
private fun hasContainsNoCache(type: FuzzyType): Boolean {
return type.nullability() != TypeNullability.NULLABLE && type.type.getMemberScope().getFunctions(containsName).any { isGoodContainsFunction(it, type.freeParameters) }
|| typesWithExtensionContains.any { type.checkIsSubtypeOf(it) != null }
}
private fun isGoodContainsFunction(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): Boolean {
if (!TypeUtils.equalTypes(function.getReturnType(), booleanType)) return false
val parameter = function.getValueParameters().singleOrNull() ?: return false
val parameterType = HeuristicSignatures.correctedParameterType(function, 0, moduleDescriptor, project) ?: parameter.getType()
val fuzzyParameterType = FuzzyType(parameterType, function.getTypeParameters() + freeTypeParams)
return fuzzyParameterType.checkIsSuperTypeOf(argumentType) != null
}
}
@@ -0,0 +1,286 @@
/*
* 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.smart
import com.intellij.codeInsight.completion.InsertHandler
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.idea.util.ShortenReferences
import java.util.HashSet
import com.intellij.codeInsight.lookup.LookupElementDecorator
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import com.intellij.codeInsight.lookup.LookupElementPresentation
import org.jetbrains.kotlin.idea.completion.*
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import com.intellij.openapi.util.Key
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.types.TypeSubstitutor
import java.util.ArrayList
import java.util.HashMap
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.idea.util.makeNotNullable
import org.jetbrains.kotlin.idea.util.nullability
import org.jetbrains.kotlin.idea.util.TypeNullability
class ArtificialElementInsertHandler(
val textBeforeCaret: String, val textAfterCaret: String, val shortenRefs: Boolean) : InsertHandler<LookupElement>{
override fun handleInsert(context: InsertionContext, item: LookupElement) {
val offset = context.getEditor().getCaretModel().getOffset()
val startOffset = offset - item.getLookupString().length()
context.getDocument().deleteString(startOffset, offset) // delete inserted lookup string
context.getDocument().insertString(startOffset, textBeforeCaret + textAfterCaret)
context.getEditor().getCaretModel().moveToOffset(startOffset + textBeforeCaret.length())
if (shortenRefs) {
shortenReferences(context, startOffset, startOffset + textBeforeCaret.length() + textAfterCaret.length())
}
}
}
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
}
fun LookupElement.addTail(tail: Tail?): LookupElement {
return when (tail) {
null -> this
Tail.COMMA -> object: LookupElementDecorator<LookupElement>(this) {
override fun handleInsert(context: InsertionContext) {
WithTailInsertHandler.commaTail().handleInsert(context, getDelegate())
}
}
Tail.RPARENTH -> object: LookupElementDecorator<LookupElement>(this) {
override fun handleInsert(context: InsertionContext) {
WithTailInsertHandler.rparenthTail().handleInsert(context, getDelegate())
}
}
Tail.ELSE -> object: LookupElementDecorator<LookupElement>(this) {
override fun handleInsert(context: InsertionContext) {
WithTailInsertHandler.elseTail().handleInsert(context, getDelegate())
}
}
}
}
fun LookupElement.addTailAndNameSimilarity(matchedExpectedInfos: Collection<ExpectedInfo>): LookupElement {
val lookupElement = addTail(mergeTails(matchedExpectedInfos.map { it.tail }))
val similarity = calcNameSimilarity(lookupElement.getLookupString(), matchedExpectedInfos)
if (similarity != 0) {
lookupElement.putUserData(NAME_SIMILARITY_KEY, similarity)
}
return lookupElement
}
class ExpectedInfoClassification private(val substitutor: TypeSubstitutor?, val makeNotNullable: Boolean) {
companion object {
val notMatches = ExpectedInfoClassification(null, false)
fun matches(substitutor: TypeSubstitutor) = ExpectedInfoClassification(substitutor, false)
fun matchesIfNotNullable(substitutor: TypeSubstitutor) = ExpectedInfoClassification(substitutor, true)
}
}
fun Collection<FuzzyType>.classifyExpectedInfo(expectedInfo: ExpectedInfo): ExpectedInfoClassification {
val stream = stream()
val substitutor = stream.map { it.checkIsSubtypeOf(expectedInfo.type) }.firstOrNull()
if (substitutor != null) {
return ExpectedInfoClassification.matches(substitutor)
}
if (stream.any { it.nullability() == TypeNullability.NULLABLE }) {
val substitutor2 = stream.map { it.makeNotNullable().checkIsSubtypeOf(expectedInfo.type) }.firstOrNull()
if (substitutor2 != null) {
return ExpectedInfoClassification.matchesIfNotNullable(substitutor2)
}
}
return ExpectedInfoClassification.notMatches
}
fun FuzzyType.classifyExpectedInfo(expectedInfo: ExpectedInfo) = listOf(this).classifyExpectedInfo(expectedInfo)
fun<TDescriptor: DeclarationDescriptor?> MutableCollection<LookupElement>.addLookupElements(
descriptor: TDescriptor,
expectedInfos: Collection<ExpectedInfo>,
infoClassifier: (ExpectedInfo) -> ExpectedInfoClassification,
lookupElementFactory: (TDescriptor) -> LookupElement?
) {
class DescriptorWrapper(val descriptor: TDescriptor) {
override fun equals(other: Any?) = other is DescriptorWrapper && descriptorsEqualWithSubstitution(this.descriptor, other.descriptor)
override fun hashCode() = if (this.descriptor != null) this.descriptor.getOriginal().hashCode() else 0
}
fun TDescriptor.wrap() = DescriptorWrapper(this)
fun DescriptorWrapper.unwrap() = this.descriptor
val matchedInfos = HashMap<DescriptorWrapper, MutableList<ExpectedInfo>>()
val makeNullableInfos = HashMap<DescriptorWrapper, MutableList<ExpectedInfo>>()
for (info in expectedInfos) {
val classification = infoClassifier(info)
if (classification.substitutor != null) {
[suppress("UNCHECKED_CAST")]
val substitutedDescriptor = descriptor?.substitute(classification.substitutor) as TDescriptor
val map = if (classification.makeNotNullable) makeNullableInfos else matchedInfos
map.getOrPut(substitutedDescriptor.wrap()) { ArrayList() }.add(info)
}
}
if (!matchedInfos.isEmpty()) {
for ((substitutedDescriptor, infos) in matchedInfos) {
val lookupElement = lookupElementFactory(substitutedDescriptor.unwrap())
if (lookupElement != null) {
add(lookupElement.addTailAndNameSimilarity(infos))
}
}
}
else {
for ((substitutedDescriptor, infos) in makeNullableInfos) {
addLookupElementsForNullable({ lookupElementFactory(substitutedDescriptor.unwrap()) }, infos)
}
}
}
private fun MutableCollection<LookupElement>.addLookupElementsForNullable(factory: () -> LookupElement?, matchedInfos: Collection<ExpectedInfo>) {
for (element in lookupElementsForNullable(factory)) {
add(element.addTailAndNameSimilarity(matchedInfos))
}
}
private fun lookupElementsForNullable(factory: () -> LookupElement?): Collection<LookupElement> {
val result = ArrayList<LookupElement>(2)
var lookupElement = factory()
if (lookupElement != null) {
lookupElement = object: LookupElementDecorator<LookupElement>(lookupElement!!) {
override fun renderElement(presentation: LookupElementPresentation) {
super.renderElement(presentation)
presentation.setItemText("!! " + presentation.getItemText())
}
override fun handleInsert(context: InsertionContext) {
WithTailInsertHandler("!!", spaceBefore = false, spaceAfter = false).handleInsert(context, getDelegate())
}
}
lookupElement = lookupElement!!.suppressAutoInsertion()
lookupElement = lookupElement!!.assignSmartCompletionPriority(SmartCompletionItemPriority.NULLABLE)
result.add(lookupElement)
}
lookupElement = factory()
if (lookupElement != null) {
lookupElement = object: LookupElementDecorator<LookupElement>(lookupElement!!) {
override fun renderElement(presentation: LookupElementPresentation) {
super.renderElement(presentation)
presentation.setItemText("?: " + presentation.getItemText())
}
override fun handleInsert(context: InsertionContext) {
WithTailInsertHandler("?:", spaceBefore = true, spaceAfter = true).handleInsert(context, getDelegate()) //TODO: code style
}
}
lookupElement = lookupElement!!.suppressAutoInsertion()
lookupElement = lookupElement!!.assignSmartCompletionPriority(SmartCompletionItemPriority.NULLABLE)
result.add(lookupElement)
}
return result
}
fun functionType(function: FunctionDescriptor): JetType? {
val extensionReceiverType = function.getExtensionReceiverParameter()?.getType()
val memberReceiverType = if (function is ConstructorDescriptor) {
val classDescriptor = function.getContainingDeclaration()
if (classDescriptor.isInner()) {
(classDescriptor.getContainingDeclaration() as? ClassifierDescriptor)?.getDefaultType()
}
else {
null
}
}
else {
(function.getContainingDeclaration() as? ClassifierDescriptor)?.getDefaultType()
}
//TODO: this is to be changed when references to member extensions supported
val receiverType = if (extensionReceiverType != null && memberReceiverType != null)
null
else
extensionReceiverType ?: memberReceiverType
return KotlinBuiltIns.getInstance().getFunctionType(function.getAnnotations(),
receiverType,
function.getValueParameters().map { it.getType() },
function.getReturnType() ?: return null)
}
fun LookupElementFactory.createLookupElement(
descriptor: DeclarationDescriptor,
resolutionFacade: ResolutionFacade,
bindingContext: BindingContext,
boldImmediateMembers: Boolean
): LookupElement {
var element = createLookupElement(resolutionFacade, descriptor, boldImmediateMembers)
if (descriptor is FunctionDescriptor && descriptor.getValueParameters().isNotEmpty()) {
element = element.keepOldArgumentListOnTab()
}
if (descriptor is ValueParameterDescriptor && bindingContext[BindingContext.AUTO_CREATED_IT, descriptor]) {
element = element.assignSmartCompletionPriority(SmartCompletionItemPriority.IT)
}
return element
}
fun <T : Any> T?.toList(): List<T> = if (this != null) listOf(this) else listOf()
fun <T : Any> T?.toSet(): Set<T> = if (this != null) setOf(this) else setOf()
enum class SmartCompletionItemPriority {
IT
TRUE
FALSE
THIS
DEFAULT
NULLABLE
INSTANTIATION
STATIC_MEMBER
ANONYMOUS_OBJECT
LAMBDA_NO_PARAMS
LAMBDA
FUNCTION_REFERENCE
NULL
INHERITOR_INSTANTIATION
}
val SMART_COMPLETION_ITEM_PRIORITY_KEY = Key<SmartCompletionItemPriority>("SMART_COMPLETION_ITEM_PRIORITY_KEY")
fun LookupElement.assignSmartCompletionPriority(priority: SmartCompletionItemPriority): LookupElement {
putUserData(SMART_COMPLETION_ITEM_PRIORITY_KEY, priority)
return this
}