FIR IDE: reuse keyword completion handlers which do not require resovle

This commit is contained in:
Ilya Kirillov
2021-05-05 14:31:13 +02:00
committed by TeamCityServer
parent 1f93eb0841
commit 36068d0b94
7 changed files with 213 additions and 83 deletions
@@ -20,12 +20,10 @@ import com.intellij.psi.*
import com.intellij.psi.impl.source.tree.LeafPsiElement
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.ProcessingContext
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
import org.jetbrains.kotlin.idea.caches.resolve.util.resolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper
import org.jetbrains.kotlin.idea.completion.handlers.createKeywordConstructLookupElement
import org.jetbrains.kotlin.idea.completion.smart.ExpectedInfoMatch
import org.jetbrains.kotlin.idea.completion.smart.SMART_COMPLETION_ITEM_PRIORITY_KEY
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletion
@@ -581,11 +579,17 @@ class BasicCompletionSession(
)
val keywordsPrefix = prefix.substringBefore('@') // if there is '@' in the prefix - use shorter prefix to not loose 'this' etc
val isUseSiteAnnotationTarget = position.prevLeaf()?.node?.elementType == KtTokens.AT
keywordCompletion.complete(expression ?: parameters.position, resultSet.prefixMatcher, isJvmModule) { lookupElement ->
val keyword = lookupElement.lookupString
if (keyword in keywordsToSkip) return@complete
val completionKeywordHandler = DefaultCompletionKeywordHandlers.defaultHandlers.getHandlerForKeyword(keyword)
if (completionKeywordHandler != null) {
val lookups = completionKeywordHandler.createLookups(parameters, expression, lookupElement, project)
collector.addElements(lookups)
return@complete
}
when (keyword) {
// if "this" is parsed correctly in the current context - insert it and all this@xxx items
"this" -> {
@@ -610,12 +614,6 @@ class BasicCompletionSession(
}
}
"break", "continue" -> {
if (expression != null) {
collector.addElements(breakOrContinueExpressionItems(expression, keyword))
}
}
"override" -> {
collector.addElement(lookupElement)
@@ -628,40 +626,6 @@ class BasicCompletionSession(
}
}
"get" -> {
collector.addElement(lookupElement)
if (!isUseSiteAnnotationTarget) {
collector.addElement(createKeywordConstructLookupElement(project, keyword, "val v:Int get()=caret"))
collector.addElement(
createKeywordConstructLookupElement(
project,
keyword,
"val v:Int get(){caret}",
trimSpacesAroundCaret = true
)
)
}
}
"set" -> {
collector.addElement(lookupElement)
if (!isUseSiteAnnotationTarget) {
collector.addElement(createKeywordConstructLookupElement(project, keyword, "var v:Int set(value)=caret"))
collector.addElement(
createKeywordConstructLookupElement(
project,
keyword,
"var v:Int set(value){caret}",
trimSpacesAroundCaret = true
)
)
}
}
"contract" -> { }
else -> collector.addElement(lookupElement)
}
}
@@ -304,7 +304,6 @@ private fun KtDeclarationWithBody.returnType(bindingContext: BindingContext): Ko
return callable.returnType
}
private fun Name?.labelNameToTail(): String = if (this != null) "@" + render() else ""
private fun createKeywordElementWithSpace(
keyword: String,
@@ -324,42 +323,7 @@ private fun createKeywordElementWithSpace(
}
}
private fun createKeywordElement(
keyword: String,
tail: String = "",
lookupObject: KeywordLookupObject = KeywordLookupObject()
): LookupElementBuilder {
var element = LookupElementBuilder.create(lookupObject, keyword + tail)
element = element.withPresentableText(keyword)
element = element.withBoldness(true)
if (tail.isNotEmpty()) {
element = element.withTailText(tail, false)
}
return element
}
fun breakOrContinueExpressionItems(position: KtElement, breakOrContinue: String): Collection<LookupElement> {
val result = ArrayList<LookupElement>()
parentsLoop@
for (parent in position.parentsWithSelf) {
when (parent) {
is KtLoopExpression -> {
if (result.isEmpty()) {
result.add(createKeywordElement(breakOrContinue))
}
val label = (parent.parent as? KtLabeledExpression)?.getLabelNameAsName()
if (label != null) {
result.add(createKeywordElement(breakOrContinue, tail = label.labelNameToTail()))
}
}
is KtDeclarationWithBody -> break@parentsLoop //TODO: support non-local break's&continue's when they are supported by compiler
}
}
return result
}
fun BasicLookupElementFactory.createLookupElementForType(type: KotlinType): LookupElement? {
if (type.isError) return null
@@ -5,11 +5,17 @@
package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.codeInsight.completion.CompletionResultSet
import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.KtExpressionWithLabel
import org.jetbrains.kotlin.psi.KtLabelReferenceExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
object FirKeywordCompletion {
private val keywordCompletion = KeywordCompletion(object : KeywordCompletion.LanguageVersionSettingProvider {
@@ -17,9 +23,29 @@ object FirKeywordCompletion {
override fun getLanguageVersionSetting(module: Module) = LanguageVersionSettingsImpl.DEFAULT // TODO
})
fun completeKeywords(result: CompletionResultSet, position: PsiElement, prefixMatcher: PrefixMatcher, isJvmModule: Boolean) {
keywordCompletion.complete(position, prefixMatcher, isJvmModule) { lookupElement ->
result.addElement(lookupElement)
fun completeKeywords(
result: CompletionResultSet,
parameters: CompletionParameters,
position: PsiElement,
prefixMatcher: PrefixMatcher,
project: Project,
isJvmModule: Boolean
) {
val reference = (position.parent as? KtSimpleNameExpression)?.mainReference
val expression = if (reference != null) { // logic is copy-n-pasted from CompletionSession.expression
if (reference.expression is KtLabelReferenceExpression) {
reference.expression.parent.parent as? KtExpressionWithLabel
} else {
reference.expression
}
} else null
keywordCompletion.complete(expression ?: position, prefixMatcher, isJvmModule) { lookupElement ->
val keyword = lookupElement.lookupString
val completionKeywordHandler = DefaultCompletionKeywordHandlers.defaultHandlers.getHandlerForKeyword(keyword)
val lookups = completionKeywordHandler
?.createLookups(parameters, expression, lookupElement, project)
?: listOf(lookupElement)
result.addAllElements(lookups)
}
}
}
@@ -143,10 +143,11 @@ private class KotlinCommonCompletionProvider(
fun addCompletions(parameters: CompletionParameters, result: CompletionResultSet) {
val originalFile = parameters.originalFile as? KtFile ?: return
val isJvmModule = TargetPlatformDetector.getPlatform(originalFile).isJvm()
val project = originalFile.project
recordOriginalFile(parameters)
FirKeywordCompletion.completeKeywords(result, parameters.position, prefixMatcher, isJvmModule = isJvmModule)
FirKeywordCompletion.completeKeywords(result, parameters, parameters.position, prefixMatcher, project, isJvmModule = isJvmModule)
val reference = (parameters.position.parent as? KtSimpleNameExpression)?.mainReference ?: return
val nameExpression = reference.expression.takeIf { it !is KtLabelReferenceExpression } ?: return
@@ -0,0 +1,116 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.completion.handlers.createKeywordConstructLookupElement
import org.jetbrains.kotlin.lexer.KtKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.psiUtil.prevLeaf
class CompletionKeywordHandlers(handlers: List<CompletionKeywordHandler>) {
constructor(vararg handlers: CompletionKeywordHandler): this(handlers.toList())
private val handlerByKeyword = handlers.associateBy { it.keyword.value }
fun getHandlerForKeyword(keyword: String): CompletionKeywordHandler? =
handlerByKeyword[keyword]
}
@OptIn(ExperimentalStdlibApi::class)
object DefaultCompletionKeywordHandlers {
private val BREAK_HANDLER = CompletionKeywordHandler(KtTokens.BREAK_KEYWORD) { _, expression, _, _ ->
if (expression != null) {
breakOrContinueExpressionItems(expression, KtTokens.BREAK_KEYWORD.value)
} else emptyList()
}
private val CONTINUE_HANDLER = CompletionKeywordHandler(KtTokens.CONTINUE_KEYWORD) { _, expression, _, _ ->
if (expression != null) {
breakOrContinueExpressionItems(expression, KtTokens.CONTINUE_KEYWORD.value)
} else emptyList()
}
private val CONTRACT_HANDLER = CompletionKeywordHandler(KtTokens.CONTRACT_KEYWORD) { _, _, _, _ ->
emptyList()
}
private val GETTER_HANDLER = CompletionKeywordHandler(KtTokens.GET_KEYWORD) { parameters, _, lookupElement, project ->
buildList {
add(lookupElement)
if (!parameters.isUseSiteAnnotationTarget) {
add(createKeywordConstructLookupElement(project, KtTokens.GET_KEYWORD.value, "val v:Int get()=caret"))
add(
createKeywordConstructLookupElement(
project,
KtTokens.GET_KEYWORD.value,
"val v:Int get(){caret}",
trimSpacesAroundCaret = true
)
)
}
}
}
private val SETTER_HANDLER = CompletionKeywordHandler(KtTokens.SET_KEYWORD) { parameters, _, lookupElement, project ->
buildList {
add(lookupElement)
if (!parameters.isUseSiteAnnotationTarget) {
add(createKeywordConstructLookupElement(project, KtTokens.SET_KEYWORD.value, "var v:Int set(value)=caret"))
add(
createKeywordConstructLookupElement(
project,
KtTokens.SET_KEYWORD.value,
"var v:Int set(value){caret}",
trimSpacesAroundCaret = true
)
)
}
}
}
val defaultHandlers = CompletionKeywordHandlers(
BREAK_HANDLER, CONTINUE_HANDLER,
GETTER_HANDLER, SETTER_HANDLER,
CONTRACT_HANDLER,
)
}
private val CompletionParameters.isUseSiteAnnotationTarget
get() = position.prevLeaf()?.node?.elementType == KtTokens.AT
abstract class CompletionKeywordHandler(
val keyword: KtKeywordToken,
) {
abstract fun createLookups(
parameters: CompletionParameters,
expression: KtExpression?,
lookup: LookupElement,
project: Project
): Collection<LookupElement>
companion object {
inline operator fun invoke(
keyword: KtKeywordToken,
crossinline create: (
parameters: CompletionParameters,
expression: KtExpression?,
lookup: LookupElement,
project: Project
) -> Collection<LookupElement>
) = object : CompletionKeywordHandler(keyword) {
override fun createLookups(
parameters: CompletionParameters,
expression: KtExpression?,
lookup: LookupElement,
project: Project
): Collection<LookupElement> = create(parameters, expression, lookup, project)
}
}
}
@@ -48,6 +48,7 @@ open class KeywordLookupObject {
override fun hashCode(): Int = javaClass.hashCode()
}
class KeywordCompletion(private val languageVersionSettingProvider: LanguageVersionSettingProvider) {
interface LanguageVersionSettingProvider {
fun getLanguageVersionSetting(element: PsiElement): LanguageVersionSettings
@@ -0,0 +1,58 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtLabeledExpression
import org.jetbrains.kotlin.psi.KtLoopExpression
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.renderer.render
import java.util.ArrayList
fun createKeywordElement(
keyword: String,
tail: String = "",
lookupObject: KeywordLookupObject = KeywordLookupObject()
): LookupElementBuilder {
var element = LookupElementBuilder.create(lookupObject, keyword + tail)
element = element.withPresentableText(keyword)
element = element.withBoldness(true)
if (tail.isNotEmpty()) {
element = element.withTailText(tail, false)
}
return element
}
fun breakOrContinueExpressionItems(position: KtElement, breakOrContinue: String): Collection<LookupElement> {
val result = ArrayList<LookupElement>()
parentsLoop@
for (parent in position.parentsWithSelf) {
when (parent) {
is KtLoopExpression -> {
if (result.isEmpty()) {
result.add(createKeywordElement(breakOrContinue))
}
val label = (parent.parent as? KtLabeledExpression)?.getLabelNameAsName()
if (label != null) {
result.add(createKeywordElement(breakOrContinue, tail = label.labelNameToTail()))
}
}
is KtDeclarationWithBody -> break@parentsLoop //TODO: support non-local break's&continue's when they are supported by compiler
}
}
return result
}
fun Name?.labelNameToTail(): String = if (this != null) "@" + render() else ""