Split module idea into idea, idea-core and idea-completion
This commit is contained in:
@@ -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)
|
||||
}
|
||||
+40
@@ -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())
|
||||
}
|
||||
+58
@@ -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]
|
||||
}
|
||||
}
|
||||
+117
@@ -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()
|
||||
}
|
||||
}
|
||||
+97
@@ -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
|
||||
}
|
||||
}
|
||||
+381
@@ -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>())
|
||||
}
|
||||
}
|
||||
+290
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+170
@@ -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) }
|
||||
}
|
||||
}
|
||||
+115
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+76
@@ -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)
|
||||
}
|
||||
}
|
||||
+73
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+40
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+137
@@ -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() }
|
||||
+262
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
+91
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+96
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+102
@@ -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
|
||||
}
|
||||
+487
@@ -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)
|
||||
}
|
||||
}
|
||||
+296
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user