Smart completion: special items with "!!" and "?:" for nullable members
This commit is contained in:
@@ -83,6 +83,9 @@
|
|||||||
name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder withTypeText(java.lang.String, boolean)'>
|
name='com.intellij.codeInsight.lookup.LookupElementBuilder com.intellij.codeInsight.lookup.LookupElementBuilder withTypeText(java.lang.String, boolean)'>
|
||||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||||
</item>
|
</item>
|
||||||
|
<item name='com.intellij.codeInsight.lookup.LookupElementDecorator LookupElementDecorator(T) 0'>
|
||||||
|
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||||
|
</item>
|
||||||
<item name='com.intellij.codeInsight.lookup.LookupElementDecorator T getDelegate()'>
|
<item name='com.intellij.codeInsight.lookup.LookupElementDecorator T getDelegate()'>
|
||||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public object ShortenReferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun process(elements: Iterable<JetElement>, elementFilter: (PsiElement) -> FilterResult) {
|
private fun process(elements: Iterable<JetElement>, elementFilter: (PsiElement) -> FilterResult) {
|
||||||
for ((file, fileElements) in elements.groupBy { element -> element.getContainingFile() as JetFile }) {
|
for ((file, fileElements) in elements.groupBy { element -> element.getContainingJetFile() }) {
|
||||||
// first resolve all qualified references - optimization
|
// first resolve all qualified references - optimization
|
||||||
val referenceToContext = JetFileReferencesResolver.resolve(file, fileElements, visitShortNames = false)
|
val referenceToContext = JetFileReferencesResolver.resolve(file, fileElements, visitShortNames = false)
|
||||||
|
|
||||||
|
|||||||
+25
-9
@@ -2,11 +2,14 @@ package org.jetbrains.jet.plugin.completion.handlers
|
|||||||
|
|
||||||
import com.intellij.codeInsight.completion.*
|
import com.intellij.codeInsight.completion.*
|
||||||
import com.intellij.codeInsight.lookup.LookupElement
|
import com.intellij.codeInsight.lookup.LookupElement
|
||||||
|
import com.intellij.openapi.editor.*
|
||||||
import com.intellij.openapi.editor.event.*
|
import com.intellij.openapi.editor.event.*
|
||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
import com.intellij.psi.PsiDocumentManager
|
import com.intellij.psi.PsiDocumentManager
|
||||||
|
|
||||||
class WithTailInsertHandler(val tailChar: Char, val spaceAfter: Boolean) : InsertHandler<LookupElement> {
|
abstract class WithTailInsertHandlerBase : InsertHandler<LookupElement> {
|
||||||
|
protected abstract fun insertTail(document: Document, offset: Int): Int
|
||||||
|
|
||||||
override fun handleInsert(context: InsertionContext, item: LookupElement) {
|
override fun handleInsert(context: InsertionContext, item: LookupElement) {
|
||||||
val document = context.getDocument()
|
val document = context.getDocument()
|
||||||
val caretModel = context.getEditor().getCaretModel()
|
val caretModel = context.getEditor().getCaretModel()
|
||||||
@@ -37,21 +40,34 @@ class WithTailInsertHandler(val tailChar: Char, val spaceAfter: Boolean) : Inser
|
|||||||
}
|
}
|
||||||
|
|
||||||
val moveCaret = caretModel.getOffset() == maxChangeOffset
|
val moveCaret = caretModel.getOffset() == maxChangeOffset
|
||||||
|
val endTailOffset = insertTail(document, maxChangeOffset)
|
||||||
|
if (moveCaret) {
|
||||||
|
caretModel.moveToOffset(endTailOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WithTailCharInsertHandler(val tailChar: Char, val spaceAfter: Boolean) : WithTailInsertHandlerBase() {
|
||||||
|
override fun insertTail(document: Document, offset: Int): Int {
|
||||||
fun isCharAt(offset: Int, c: Char) = offset < document.getTextLength() && document.getText(TextRange(offset, offset + 1))[0] == c
|
fun isCharAt(offset: Int, c: Char) = offset < document.getTextLength() && document.getText(TextRange(offset, offset + 1))[0] == c
|
||||||
|
|
||||||
if (isCharAt(maxChangeOffset, tailChar)) {
|
if (isCharAt(offset, tailChar)) {
|
||||||
document.deleteString(maxChangeOffset, maxChangeOffset + 1)
|
document.deleteString(offset, offset + 1)
|
||||||
|
|
||||||
if (spaceAfter && isCharAt(maxChangeOffset, ' ')) {
|
if (spaceAfter && isCharAt(offset, ' ')) {
|
||||||
document.deleteString(maxChangeOffset, maxChangeOffset + 1)
|
document.deleteString(offset, offset + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val textToInsert = if (spaceAfter) tailChar + " " else tailChar.toString()
|
val textToInsert = if (spaceAfter) tailChar + " " else tailChar.toString()
|
||||||
document.insertString(maxChangeOffset, textToInsert)
|
document.insertString(offset, textToInsert)
|
||||||
if (moveCaret) {
|
return offset + textToInsert.length
|
||||||
caretModel.moveToOffset(maxChangeOffset + textToInsert.length)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class WithTailStringInsertHandler(val tail: String) : WithTailInsertHandlerBase() {
|
||||||
|
override fun insertTail(document: Document, offset: Int): Int {
|
||||||
|
document.insertString(offset, tail)
|
||||||
|
return offset + tail.length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ class LambdaItems(val project: Project) {
|
|||||||
val offerNoParametersLambda = singleSignatureLength == 0 || singleSignatureLength == 1
|
val offerNoParametersLambda = singleSignatureLength == 0 || singleSignatureLength == 1
|
||||||
if (offerNoParametersLambda) {
|
if (offerNoParametersLambda) {
|
||||||
val lookupElement = createLookupElement("{...}", "{ ", " }", shortenRefs = false)
|
val lookupElement = createLookupElement("{...}", "{ ", " }", shortenRefs = false)
|
||||||
collection.add(addTailToLookupElement(lookupElement, functionExpectedInfos))
|
collection.add(lookupElement.addTail(functionExpectedInfos))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (singleSignatureLength != 0) {
|
if (singleSignatureLength != 0) {
|
||||||
@@ -44,7 +44,7 @@ class LambdaItems(val project: Project) {
|
|||||||
val lookupElement = LookupElementBuilder.create(lookupString)
|
val lookupElement = LookupElementBuilder.create(lookupString)
|
||||||
.withInsertHandler(LambdaInsertHandler(functionType, useExplicitTypes))
|
.withInsertHandler(LambdaInsertHandler(functionType, useExplicitTypes))
|
||||||
.suppressAutoInsertion()
|
.suppressAutoInsertion()
|
||||||
collection.add(addTailToLookupElement(lookupElement, functionExpectedInfos.filter { it.`type` == functionType }))
|
collection.add(lookupElement.addTail(functionExpectedInfos.filter { it.`type` == functionType }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,13 +55,16 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
|
|||||||
for (descriptor in referenceVariants) {
|
for (descriptor in referenceVariants) {
|
||||||
if (itemsToSkip.contains(descriptor)) continue
|
if (itemsToSkip.contains(descriptor)) continue
|
||||||
|
|
||||||
val matchedExpectedInfos = expectedInfos.filter { expectedInfo ->
|
val types = typesWithAutoCasts(descriptor)
|
||||||
typesWithAutoCasts(descriptor).any { it.isSubtypeOf(expectedInfo.`type`) }
|
val nonNullTypes = types.map { TypeUtils.makeNotNullable(it) }
|
||||||
}
|
val classifier = { (expectedInfo: ExpectedInfo) ->
|
||||||
if (matchedExpectedInfos.isNotEmpty()) {
|
when {
|
||||||
val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor)
|
types.any { it.isSubtypeOf(expectedInfo.`type`) } -> ExpectedInfoClassification.MATCHES
|
||||||
result.add(addTailToLookupElement(lookupElement, matchedExpectedInfos))
|
nonNullTypes.any { it.isSubtypeOf(expectedInfo.`type`) } -> ExpectedInfoClassification.MAKE_NOT_NULLABLE
|
||||||
|
else -> ExpectedInfoClassification.NOT_MATCHES
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
result.addLookupElements(expectedInfos, classifier, { DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor) })
|
||||||
|
|
||||||
if (receiver == null) {
|
if (receiver == null) {
|
||||||
toFunctionReferenceLookupElement(descriptor, functionExpectedInfos)?.let { result.add(it) }
|
toFunctionReferenceLookupElement(descriptor, functionExpectedInfos)?.let { result.add(it) }
|
||||||
@@ -129,7 +132,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return addTailToLookupElement(lookupElement, matchedExpectedInfos)
|
return lookupElement.addTail(matchedExpectedInfos)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (descriptor is SimpleFunctionDescriptor) {
|
if (descriptor is SimpleFunctionDescriptor) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import com.intellij.codeInsight.completion.InsertionContext
|
|||||||
import org.jetbrains.jet.plugin.project.ResolveSessionForBodies
|
import org.jetbrains.jet.plugin.project.ResolveSessionForBodies
|
||||||
import org.jetbrains.jet.lang.resolve.BindingContext
|
import org.jetbrains.jet.lang.resolve.BindingContext
|
||||||
import org.jetbrains.jet.lang.psi.JetExpression
|
import org.jetbrains.jet.lang.psi.JetExpression
|
||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
// adds java static members, enum members and members from class object
|
// adds java static members, enum members and members from class object
|
||||||
class StaticMembers(val bindingContext: BindingContext, val resolveSession: ResolveSessionForBodies) {
|
class StaticMembers(val bindingContext: BindingContext, val resolveSession: ResolveSessionForBodies) {
|
||||||
@@ -45,15 +46,27 @@ class StaticMembers(val bindingContext: BindingContext, val resolveSession: Reso
|
|||||||
fun processMember(descriptor: DeclarationDescriptor) {
|
fun processMember(descriptor: DeclarationDescriptor) {
|
||||||
if (descriptor is DeclarationDescriptorWithVisibility && !Visibilities.isVisible(descriptor, scope.getContainingDeclaration())) return
|
if (descriptor is DeclarationDescriptorWithVisibility && !Visibilities.isVisible(descriptor, scope.getContainingDeclaration())) return
|
||||||
|
|
||||||
val matchedExpectedInfos = expectedInfos.filter {
|
val classifier: (ExpectedInfo) -> ExpectedInfoClassification
|
||||||
expectedInfo ->
|
if (descriptor is CallableDescriptor) {
|
||||||
descriptor is CallableDescriptor && descriptor.getReturnType()?.let { it.isSubtypeOf(expectedInfo.`type`) } ?: false
|
val returnType = descriptor.getReturnType()
|
||||||
|| DescriptorUtils.isEnumEntry(descriptor) /* we do not need to check type of enum entry because it's taken from proper enum */
|
if (returnType == null) return
|
||||||
|
classifier = {
|
||||||
|
expectedInfo ->
|
||||||
|
when {
|
||||||
|
returnType.isSubtypeOf(expectedInfo.`type`) -> ExpectedInfoClassification.MATCHES
|
||||||
|
returnType.isNullable() && TypeUtils.makeNotNullable(returnType).isSubtypeOf(expectedInfo.`type`) -> ExpectedInfoClassification.MAKE_NOT_NULLABLE
|
||||||
|
else -> ExpectedInfoClassification.NOT_MATCHES
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (DescriptorUtils.isEnumEntry(descriptor)) {
|
||||||
|
classifier = { ExpectedInfoClassification.MATCHES } /* we do not need to check type of enum entry because it's taken from proper enum */
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if (matchedExpectedInfos.isEmpty()) return
|
|
||||||
|
|
||||||
val lookupElement = createLookupElement(descriptor, classDescriptor)
|
collection.addLookupElements(expectedInfos, classifier, { createLookupElement(descriptor, classDescriptor) })
|
||||||
collection.add(addTailToLookupElement(lookupElement, matchedExpectedInfos))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (classDescriptor is JavaClassDescriptor) {
|
if (classDescriptor is JavaClassDescriptor) {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import org.jetbrains.jet.lang.psi.JetValueArgumentList
|
|||||||
import org.jetbrains.jet.lang.psi.JetCallExpression
|
import org.jetbrains.jet.lang.psi.JetCallExpression
|
||||||
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
|
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
|
||||||
import org.jetbrains.jet.lang.resolve.BindingContext
|
import org.jetbrains.jet.lang.resolve.BindingContext
|
||||||
|
import org.jetbrains.jet.lang.types.TypeUtils
|
||||||
|
|
||||||
class ThisItems(val bindingContext: BindingContext) {
|
class ThisItems(val bindingContext: BindingContext) {
|
||||||
public fun addToCollection(collection: MutableCollection<LookupElement>, context: JetExpression, expectedInfos: Collection<ExpectedInfo>) {
|
public fun addToCollection(collection: MutableCollection<LookupElement>, context: JetExpression, expectedInfos: Collection<ExpectedInfo>) {
|
||||||
@@ -27,15 +28,21 @@ class ThisItems(val bindingContext: BindingContext) {
|
|||||||
for (i in 0..receivers.size - 1) {
|
for (i in 0..receivers.size - 1) {
|
||||||
val receiver = receivers[i]
|
val receiver = receivers[i]
|
||||||
val thisType = receiver.getType()
|
val thisType = receiver.getType()
|
||||||
val matchedExpectedInfos = expectedInfos.filter { thisType.isSubtypeOf(it.`type`) }
|
val classifier = { (expectedInfo: ExpectedInfo) ->
|
||||||
if (matchedExpectedInfos.notEmpty) {
|
when {
|
||||||
//TODO: use this code when KT-4258 fixed
|
thisType.isSubtypeOf(expectedInfo.`type`) -> ExpectedInfoClassification.MATCHES
|
||||||
//val expressionText = if (i == 0) "this" else "this@" + (thisQualifierName(receiver, bindingContext) ?: continue)
|
thisType.isNullable() && TypeUtils.makeNotNullable(thisType).isSubtypeOf(expectedInfo.`type`) -> ExpectedInfoClassification.MAKE_NOT_NULLABLE
|
||||||
val qualifier = if (i == 0) null else thisQualifierName(receiver) ?: continue
|
else -> ExpectedInfoClassification.NOT_MATCHES
|
||||||
val expressionText = if (qualifier == null) "this" else "this@" + qualifier
|
}
|
||||||
val lookupElement = LookupElementBuilder.create(expressionText).withTypeText(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(thisType))
|
|
||||||
collection.add(addTailToLookupElement(lookupElement, matchedExpectedInfos))
|
|
||||||
}
|
}
|
||||||
|
fun lookupElementFactory(): LookupElement? {
|
||||||
|
//TODO: use this code when KT-4258 fixed
|
||||||
|
//val expressionText = if (i == 0) "this" else "this@" + (thisQualifierName(receiver, bindingContext) ?: return null)
|
||||||
|
val qualifier = if (i == 0) null else (thisQualifierName(receiver) ?: return null)
|
||||||
|
val expressionText = if (qualifier == null) "this" else "this@" + qualifier
|
||||||
|
return LookupElementBuilder.create(expressionText).withTypeText(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(thisType))
|
||||||
|
}
|
||||||
|
collection.addLookupElements(expectedInfos, classifier, ::lookupElementFactory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,6 @@ class TypeInstantiationItems(val bindingContext: BindingContext, val resolveSess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.add(addTailToLookupElement(lookupElement, tail))
|
collection.add(lookupElement.addTail(tail))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,18 @@ import com.intellij.codeInsight.lookup.LookupElement
|
|||||||
import com.intellij.codeInsight.completion.InsertionContext
|
import com.intellij.codeInsight.completion.InsertionContext
|
||||||
import com.intellij.psi.PsiDocumentManager
|
import com.intellij.psi.PsiDocumentManager
|
||||||
import org.jetbrains.jet.lang.psi.JetFile
|
import org.jetbrains.jet.lang.psi.JetFile
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
|
||||||
import org.jetbrains.jet.lang.psi.JetElement
|
|
||||||
import org.jetbrains.jet.plugin.codeInsight.ShortenReferences
|
import org.jetbrains.jet.plugin.codeInsight.ShortenReferences
|
||||||
import java.util.HashSet
|
import java.util.HashSet
|
||||||
import com.intellij.codeInsight.lookup.LookupElementDecorator
|
import com.intellij.codeInsight.lookup.LookupElementDecorator
|
||||||
import org.jetbrains.jet.plugin.completion.handlers.WithTailInsertHandler
|
import org.jetbrains.jet.plugin.completion.handlers.WithTailCharInsertHandler
|
||||||
import com.intellij.codeInsight.lookup.AutoCompletionPolicy
|
import com.intellij.codeInsight.lookup.AutoCompletionPolicy
|
||||||
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor
|
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor
|
||||||
import org.jetbrains.jet.lang.types.JetType
|
import org.jetbrains.jet.lang.types.JetType
|
||||||
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
|
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
|
||||||
import org.jetbrains.jet.lang.types.checker.JetTypeChecker
|
import org.jetbrains.jet.lang.types.checker.JetTypeChecker
|
||||||
|
import com.intellij.codeInsight.lookup.LookupElementPresentation
|
||||||
|
import org.jetbrains.jet.plugin.completion.handlers.WithTailStringInsertHandler
|
||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
class ArtificialElementInsertHandler(
|
class ArtificialElementInsertHandler(
|
||||||
val textBeforeCaret: String, val textAfterCaret: String, val shortenRefs: Boolean) : InsertHandler<LookupElement>{
|
val textBeforeCaret: String, val textAfterCaret: String, val shortenRefs: Boolean) : InsertHandler<LookupElement>{
|
||||||
@@ -42,29 +43,94 @@ fun mergeTails(tails: Collection<Tail?>): Tail? {
|
|||||||
return if (HashSet(tails).size == 1) tails.first() else null
|
return if (HashSet(tails).size == 1) tails.first() else null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addTailToLookupElement(lookupElement: LookupElement, tail: Tail?): LookupElement {
|
fun LookupElement.addTail(tail: Tail?): LookupElement {
|
||||||
return when (tail) {
|
return when (tail) {
|
||||||
null -> lookupElement
|
null -> this
|
||||||
|
|
||||||
Tail.COMMA -> object: LookupElementDecorator<LookupElement>(lookupElement) {
|
Tail.COMMA -> object: LookupElementDecorator<LookupElement>(this) {
|
||||||
override fun handleInsert(context: InsertionContext) {
|
override fun handleInsert(context: InsertionContext) {
|
||||||
WithTailInsertHandler(',', true /*TODO: use code style option*/).handleInsert(context, lookupElement)
|
WithTailCharInsertHandler(',', true /*TODO: use code style option*/).handleInsert(context, getDelegate())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Tail.PARENTHESIS -> object: LookupElementDecorator<LookupElement>(lookupElement) {
|
Tail.PARENTHESIS -> object: LookupElementDecorator<LookupElement>(this) {
|
||||||
override fun handleInsert(context: InsertionContext) {
|
override fun handleInsert(context: InsertionContext) {
|
||||||
WithTailInsertHandler(')', false).handleInsert(context, lookupElement)
|
WithTailCharInsertHandler(')', false).handleInsert(context, getDelegate())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addTailToLookupElement(lookupElement: LookupElement, matchedExpectedInfos: Collection<ExpectedInfo>): LookupElement
|
fun LookupElement.addTail(matchedExpectedInfos: Collection<ExpectedInfo>): LookupElement
|
||||||
= addTailToLookupElement(lookupElement, mergeTails(matchedExpectedInfos.map { it.tail }))
|
= addTail(mergeTails(matchedExpectedInfos.map { it.tail }))
|
||||||
|
|
||||||
fun LookupElement.suppressAutoInsertion() = AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(this)
|
fun LookupElement.suppressAutoInsertion() = AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(this)
|
||||||
|
|
||||||
|
enum class ExpectedInfoClassification {
|
||||||
|
MATCHES
|
||||||
|
MAKE_NOT_NULLABLE
|
||||||
|
NOT_MATCHES
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MutableCollection<LookupElement>.addLookupElements(expectedInfos: Collection<ExpectedInfo>,
|
||||||
|
infoClassifier: (ExpectedInfo) -> ExpectedInfoClassification,
|
||||||
|
lookupElementFactory: () -> LookupElement?) {
|
||||||
|
val matchedInfos = ArrayList<ExpectedInfo>()
|
||||||
|
val matchedInfosNotNullable = ArrayList<ExpectedInfo>()
|
||||||
|
for (info in expectedInfos) {
|
||||||
|
when (infoClassifier(info)) {
|
||||||
|
ExpectedInfoClassification.MATCHES -> matchedInfos.add(info)
|
||||||
|
ExpectedInfoClassification.MAKE_NOT_NULLABLE -> matchedInfosNotNullable.add(info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchedInfos.isNotEmpty()) {
|
||||||
|
val lookupElement = lookupElementFactory()
|
||||||
|
if (lookupElement != null) {
|
||||||
|
add(lookupElement.addTail(matchedInfos))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (matchedInfosNotNullable.isNotEmpty()) {
|
||||||
|
addLookupElementsForNullable(lookupElementFactory, matchedInfosNotNullable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MutableCollection<LookupElement>.addLookupElementsForNullable(factory: () -> LookupElement?, matchedInfos: Collection<ExpectedInfo>) {
|
||||||
|
run {
|
||||||
|
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) {
|
||||||
|
WithTailStringInsertHandler("!!").handleInsert(context, getDelegate())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lookupElement = lookupElement!!.suppressAutoInsertion()
|
||||||
|
add(lookupElement!!.addTail(matchedInfos))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
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) {
|
||||||
|
WithTailStringInsertHandler(" ?: ").handleInsert(context, getDelegate()) //TODO: code style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lookupElement = lookupElement!!.suppressAutoInsertion()
|
||||||
|
add(lookupElement!!.addTail(matchedInfos))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun functionType(function: FunctionDescriptor): JetType? {
|
fun functionType(function: FunctionDescriptor): JetType? {
|
||||||
return KotlinBuiltIns.getInstance().getKFunctionType(function.getAnnotations(),
|
return KotlinBuiltIns.getInstance().getKFunctionType(function.getAnnotations(),
|
||||||
null,
|
null,
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fun foo(s: String, c: Char){ }
|
||||||
|
|
||||||
|
fun bar(sss: String?) {
|
||||||
|
foo(<caret>)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ELEMENT_TEXT: "!! sss"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fun foo(s: String, c: Char){ }
|
||||||
|
|
||||||
|
fun bar(sss: String?) {
|
||||||
|
foo(sss!!, <caret>)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ELEMENT_TEXT: "!! sss"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
fun foo(s: String){ }
|
||||||
|
|
||||||
|
fun getString(i: Int): String?{}
|
||||||
|
|
||||||
|
fun bar() {
|
||||||
|
foo(<caret>)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ELEMENT_TEXT: "?: getString(i: Int)"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
fun foo(s: String){ }
|
||||||
|
|
||||||
|
fun getString(i: Int): String?{}
|
||||||
|
|
||||||
|
fun bar() {
|
||||||
|
foo(getString(<caret>) ?: )
|
||||||
|
}
|
||||||
|
|
||||||
|
// ELEMENT_TEXT: "?: getString(i: Int)"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
class K {
|
||||||
|
class object {
|
||||||
|
fun bar(): K? = K()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun foo(){
|
||||||
|
val k : K = <caret>
|
||||||
|
}
|
||||||
|
|
||||||
|
// ELEMENT_TEXT: "!! K.bar()"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
class K {
|
||||||
|
class object {
|
||||||
|
fun bar(): K? = K()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun foo(){
|
||||||
|
val k : K = K.bar()!!<caret>
|
||||||
|
}
|
||||||
|
|
||||||
|
// ELEMENT_TEXT: "!! K.bar()"
|
||||||
@@ -7,4 +7,6 @@ class Foo(val prop1 : String?, val prop2 : String?){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EXIST: prop1
|
// EXIST: prop1
|
||||||
// ABSENT: prop2
|
// ABSENT: { lookupString:"prop2", itemText:"prop2" }
|
||||||
|
// EXIST: { lookupString:"prop2", itemText:"!! prop2" }
|
||||||
|
// EXIST: { lookupString:"prop2", itemText:"?: prop2" }
|
||||||
|
|||||||
@@ -17,5 +17,7 @@ fun foo(){
|
|||||||
// EXIST: { lookupString:"K.foo", itemText:"K.foo", tailText:" (sample)", typeText:"K" }
|
// EXIST: { lookupString:"K.foo", itemText:"K.foo", tailText:" (sample)", typeText:"K" }
|
||||||
// EXIST: { lookupString:"K.bar", itemText:"K.bar()", tailText:" (sample)", typeText:"K" }
|
// EXIST: { lookupString:"K.bar", itemText:"K.bar()", tailText:" (sample)", typeText:"K" }
|
||||||
// ABSENT: K.x
|
// ABSENT: K.x
|
||||||
// ABSENT: K.kk
|
// ABSENT: { itemText:"K.kk" }
|
||||||
|
// EXIST: { lookupString:"K.kk", itemText:"!! K.kk", tailText:" (sample)", typeText:"K?" }
|
||||||
|
// EXIST: { lookupString:"K.kk", itemText:"?: K.kk", tailText:" (sample)", typeText:"K?" }
|
||||||
// ABSENT: K.privateVal
|
// ABSENT: K.privateVal
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ fun f3() : String{}
|
|||||||
// EXIST: p1
|
// EXIST: p1
|
||||||
// EXIST: p2
|
// EXIST: p2
|
||||||
// ABSENT: p3
|
// ABSENT: p3
|
||||||
// ABSENT: p4
|
// ABSENT: { lookupString:"p4", itemText:"p4" }
|
||||||
|
// EXIST: { lookupString:"p4", itemText:"!! p4" }
|
||||||
|
// EXIST: { lookupString:"p4", itemText:"?: p4" }
|
||||||
// EXIST: f1
|
// EXIST: f1
|
||||||
// EXIST: f2
|
// EXIST: f2
|
||||||
// ABSENT: f3
|
// ABSENT: f3
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
fun String?.foo(){
|
||||||
|
val s : String = <caret>
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXIST: { lookupString:"this", itemText: "!! this", typeText:"String?" }
|
||||||
|
// EXIST: { lookupString:"this", itemText: "?: this", typeText:"String?" }
|
||||||
@@ -256,6 +256,11 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
|
|||||||
doTest("idea/testData/completion/smart/NotSillyAssignment.kt");
|
doTest("idea/testData/completion/smart/NotSillyAssignment.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("NullableThis.kt")
|
||||||
|
public void testNullableThis() throws Exception {
|
||||||
|
doTest("idea/testData/completion/smart/NullableThis.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("OverloadedConstructorArgument.kt")
|
@TestMetadata("OverloadedConstructorArgument.kt")
|
||||||
public void testOverloadedConstructorArgument() throws Exception {
|
public void testOverloadedConstructorArgument() throws Exception {
|
||||||
doTest("idea/testData/completion/smart/OverloadedConstructorArgument.kt");
|
doTest("idea/testData/completion/smart/OverloadedConstructorArgument.kt");
|
||||||
|
|||||||
+3
-1
@@ -37,6 +37,7 @@ import org.jetbrains.jet.InTextDirectivesUtils
|
|||||||
abstract class AbstractSmartCompletionHandlerTest() : CompletionHandlerTestBase() {
|
abstract class AbstractSmartCompletionHandlerTest() : CompletionHandlerTestBase() {
|
||||||
private val INVOCATION_COUNT_PREFIX = "INVOCATION_COUNT:"
|
private val INVOCATION_COUNT_PREFIX = "INVOCATION_COUNT:"
|
||||||
private val LOOKUP_STRING_PREFIX = "ELEMENT:"
|
private val LOOKUP_STRING_PREFIX = "ELEMENT:"
|
||||||
|
private val ELEMENT_TEXT_PREFIX = "ELEMENT_TEXT:"
|
||||||
private val TAIL_TEXT_PREFIX = "TAIL_TEXT:"
|
private val TAIL_TEXT_PREFIX = "TAIL_TEXT:"
|
||||||
private val COMPLETION_CHAR_PREFIX = "CHAR:"
|
private val COMPLETION_CHAR_PREFIX = "CHAR:"
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ abstract class AbstractSmartCompletionHandlerTest() : CompletionHandlerTestBase(
|
|||||||
val fileText = fixture.getFile()!!.getText()
|
val fileText = fixture.getFile()!!.getText()
|
||||||
val invocationCount = InTextDirectivesUtils.getPrefixedInt(fileText, INVOCATION_COUNT_PREFIX) ?: 1
|
val invocationCount = InTextDirectivesUtils.getPrefixedInt(fileText, INVOCATION_COUNT_PREFIX) ?: 1
|
||||||
val lookupString = InTextDirectivesUtils.findStringWithPrefixes(fileText, LOOKUP_STRING_PREFIX)
|
val lookupString = InTextDirectivesUtils.findStringWithPrefixes(fileText, LOOKUP_STRING_PREFIX)
|
||||||
|
val itemText = InTextDirectivesUtils.findStringWithPrefixes(fileText, ELEMENT_TEXT_PREFIX)
|
||||||
val tailText = InTextDirectivesUtils.findStringWithPrefixes(fileText, TAIL_TEXT_PREFIX)
|
val tailText = InTextDirectivesUtils.findStringWithPrefixes(fileText, TAIL_TEXT_PREFIX)
|
||||||
val completionCharString = InTextDirectivesUtils.findStringWithPrefixes(fileText, COMPLETION_CHAR_PREFIX)
|
val completionCharString = InTextDirectivesUtils.findStringWithPrefixes(fileText, COMPLETION_CHAR_PREFIX)
|
||||||
val completionChar = when(completionCharString) {
|
val completionChar = when(completionCharString) {
|
||||||
@@ -56,6 +58,6 @@ abstract class AbstractSmartCompletionHandlerTest() : CompletionHandlerTestBase(
|
|||||||
"\\t" -> '\t'
|
"\\t" -> '\t'
|
||||||
else -> error("Uknown completion char")
|
else -> error("Uknown completion char")
|
||||||
}
|
}
|
||||||
doTestWithTextLoaded(invocationCount, lookupString, tailText, completionChar)
|
doTestWithTextLoaded(invocationCount, lookupString, itemText, tailText, completionChar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,20 +42,24 @@ public abstract class CompletionHandlerTestBase() : JetLightCodeInsightFixtureTe
|
|||||||
|
|
||||||
protected override fun setUp() {
|
protected override fun setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
fixture = myFixture!!
|
fixture = myFixture
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun doTest() : Unit = doTest(2, null, null, '\n')
|
protected fun doTest() : Unit = doTest(2, null, null, null, '\n')
|
||||||
|
|
||||||
protected fun doTest(time : Int, lookupString : String?, tailText : String?, completionChar : Char) {
|
protected fun doTest(time : Int, lookupString : String?, tailText : String?, completionChar : Char) {
|
||||||
fixture.configureByFile(fileName())
|
doTest(time, lookupString, null, tailText, completionChar)
|
||||||
doTestWithTextLoaded(time, lookupString, tailText, completionChar)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun doTestWithTextLoaded(time : Int, lookupString : String?, tailText : String?, completionChar : Char) {
|
protected fun doTest(time : Int, lookupString : String?, itemText: String?, tailText : String?, completionChar : Char) {
|
||||||
if (lookupString != null || tailText != null) {
|
fixture.configureByFile(fileName())
|
||||||
|
doTestWithTextLoaded(time, lookupString, itemText, tailText, completionChar)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun doTestWithTextLoaded(time : Int, lookupString : String?, itemText: String?, tailText : String?, completionChar : Char) {
|
||||||
|
if (lookupString != null || itemText != null || tailText != null) {
|
||||||
fixture.complete(completionType, time)
|
fixture.complete(completionType, time)
|
||||||
val item = getExistentLookupElement(lookupString, tailText)
|
val item = getExistentLookupElement(lookupString, itemText, tailText)
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
selectItem(item, completionChar)
|
selectItem(item, completionChar)
|
||||||
}
|
}
|
||||||
@@ -70,38 +74,42 @@ public abstract class CompletionHandlerTestBase() : JetLightCodeInsightFixtureTe
|
|||||||
fixture.checkResultByFile(afterFileName())
|
fixture.checkResultByFile(afterFileName())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getExistentLookupElement(lookupString : String?, tailText : String?) : LookupElement? {
|
private fun getExistentLookupElement(lookupString : String?, itemText: String?, tailText : String?) : LookupElement? {
|
||||||
val lookup = LookupManager.getInstance(getProject())?.getActiveLookup() as LookupImpl?
|
val lookup = LookupManager.getInstance(getProject())?.getActiveLookup() as LookupImpl?
|
||||||
if (lookup == null) return null
|
if (lookup == null) return null
|
||||||
|
|
||||||
var foundElement : LookupElement? = null
|
var foundElement : LookupElement? = null
|
||||||
val presentation = LookupElementPresentation()
|
val presentation = LookupElementPresentation()
|
||||||
for (lookupElement in lookup.getItems()) {
|
for (lookupElement in lookup.getItems()) {
|
||||||
val lookupOk : Boolean
|
val lookupOk = if (lookupString != null) lookupElement.getLookupString().contains(lookupString) else true
|
||||||
if (lookupString != null) {
|
|
||||||
lookupOk = lookupElement.getLookupString().contains(lookupString)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
lookupOk = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lookupOk) {
|
if (lookupOk) {
|
||||||
val tailOk : Boolean
|
lookupElement.renderElement(presentation)
|
||||||
if (tailText != null) {
|
|
||||||
lookupElement.renderElement(presentation)
|
val textOk = if (itemText != null) {
|
||||||
val itemTailText : String? = presentation.getTailText()
|
val itemItemText = presentation.getItemText()
|
||||||
tailOk = itemTailText != null && itemTailText.contains(tailText)
|
itemItemText != null && itemItemText.contains(itemText)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
tailOk = true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tailOk) {
|
if (textOk) {
|
||||||
if (foundElement != null) {
|
val tailOk = if (tailText != null) {
|
||||||
Assert.fail("Several elements satisfy to completion restrictions")
|
val itemTailText = presentation.getTailText()
|
||||||
|
itemTailText != null && itemTailText.contains(tailText)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
foundElement = lookupElement
|
if (tailOk) {
|
||||||
|
if (foundElement != null) {
|
||||||
|
Assert.fail("Several elements satisfy to completion restrictions")
|
||||||
|
}
|
||||||
|
|
||||||
|
foundElement = lookupElement
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
@@ -246,6 +246,21 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion
|
|||||||
doTest("idea/testData/completion/handlers/smart/MergeTail4.kt");
|
doTest("idea/testData/completion/handlers/smart/MergeTail4.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("NullableValue1.kt")
|
||||||
|
public void testNullableValue1() throws Exception {
|
||||||
|
doTest("idea/testData/completion/handlers/smart/NullableValue1.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("NullableValue2.kt")
|
||||||
|
public void testNullableValue2() throws Exception {
|
||||||
|
doTest("idea/testData/completion/handlers/smart/NullableValue2.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("NullableValue3.kt")
|
||||||
|
public void testNullableValue3() throws Exception {
|
||||||
|
doTest("idea/testData/completion/handlers/smart/NullableValue3.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("TabReplaceComma1.kt")
|
@TestMetadata("TabReplaceComma1.kt")
|
||||||
public void testTabReplaceComma1() throws Exception {
|
public void testTabReplaceComma1() throws Exception {
|
||||||
doTest("idea/testData/completion/handlers/smart/TabReplaceComma1.kt");
|
doTest("idea/testData/completion/handlers/smart/TabReplaceComma1.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user