FIR IDE: reuse keyword completion handlers which do not require resovle
This commit is contained in:
committed by
TeamCityServer
parent
1f93eb0841
commit
36068d0b94
+7
-43
@@ -20,12 +20,10 @@ import com.intellij.psi.*
|
|||||||
import com.intellij.psi.impl.source.tree.LeafPsiElement
|
import com.intellij.psi.impl.source.tree.LeafPsiElement
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
import com.intellij.util.ProcessingContext
|
import com.intellij.util.ProcessingContext
|
||||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.util.resolveToDescriptor
|
import org.jetbrains.kotlin.idea.caches.resolve.util.resolveToDescriptor
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper
|
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.ExpectedInfoMatch
|
||||||
import org.jetbrains.kotlin.idea.completion.smart.SMART_COMPLETION_ITEM_PRIORITY_KEY
|
import org.jetbrains.kotlin.idea.completion.smart.SMART_COMPLETION_ITEM_PRIORITY_KEY
|
||||||
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletion
|
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 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 ->
|
keywordCompletion.complete(expression ?: parameters.position, resultSet.prefixMatcher, isJvmModule) { lookupElement ->
|
||||||
val keyword = lookupElement.lookupString
|
val keyword = lookupElement.lookupString
|
||||||
if (keyword in keywordsToSkip) return@complete
|
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) {
|
when (keyword) {
|
||||||
// if "this" is parsed correctly in the current context - insert it and all this@xxx items
|
// if "this" is parsed correctly in the current context - insert it and all this@xxx items
|
||||||
"this" -> {
|
"this" -> {
|
||||||
@@ -610,12 +614,6 @@ class BasicCompletionSession(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"break", "continue" -> {
|
|
||||||
if (expression != null) {
|
|
||||||
collector.addElements(breakOrContinueExpressionItems(expression, keyword))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"override" -> {
|
"override" -> {
|
||||||
collector.addElement(lookupElement)
|
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)
|
else -> collector.addElement(lookupElement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -304,7 +304,6 @@ private fun KtDeclarationWithBody.returnType(bindingContext: BindingContext): Ko
|
|||||||
return callable.returnType
|
return callable.returnType
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Name?.labelNameToTail(): String = if (this != null) "@" + render() else ""
|
|
||||||
|
|
||||||
private fun createKeywordElementWithSpace(
|
private fun createKeywordElementWithSpace(
|
||||||
keyword: String,
|
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? {
|
fun BasicLookupElementFactory.createLookupElementForType(type: KotlinType): LookupElement? {
|
||||||
if (type.isError) return null
|
if (type.isError) return null
|
||||||
|
|||||||
@@ -5,11 +5,17 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.completion
|
package org.jetbrains.kotlin.idea.completion
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.completion.CompletionParameters
|
||||||
import com.intellij.codeInsight.completion.CompletionResultSet
|
import com.intellij.codeInsight.completion.CompletionResultSet
|
||||||
import com.intellij.codeInsight.completion.PrefixMatcher
|
import com.intellij.codeInsight.completion.PrefixMatcher
|
||||||
import com.intellij.openapi.module.Module
|
import com.intellij.openapi.module.Module
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
|
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 {
|
object FirKeywordCompletion {
|
||||||
private val keywordCompletion = KeywordCompletion(object : KeywordCompletion.LanguageVersionSettingProvider {
|
private val keywordCompletion = KeywordCompletion(object : KeywordCompletion.LanguageVersionSettingProvider {
|
||||||
@@ -17,9 +23,29 @@ object FirKeywordCompletion {
|
|||||||
override fun getLanguageVersionSetting(module: Module) = LanguageVersionSettingsImpl.DEFAULT // TODO
|
override fun getLanguageVersionSetting(module: Module) = LanguageVersionSettingsImpl.DEFAULT // TODO
|
||||||
})
|
})
|
||||||
|
|
||||||
fun completeKeywords(result: CompletionResultSet, position: PsiElement, prefixMatcher: PrefixMatcher, isJvmModule: Boolean) {
|
fun completeKeywords(
|
||||||
keywordCompletion.complete(position, prefixMatcher, isJvmModule) { lookupElement ->
|
result: CompletionResultSet,
|
||||||
result.addElement(lookupElement)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-1
@@ -143,10 +143,11 @@ private class KotlinCommonCompletionProvider(
|
|||||||
fun addCompletions(parameters: CompletionParameters, result: CompletionResultSet) {
|
fun addCompletions(parameters: CompletionParameters, result: CompletionResultSet) {
|
||||||
val originalFile = parameters.originalFile as? KtFile ?: return
|
val originalFile = parameters.originalFile as? KtFile ?: return
|
||||||
val isJvmModule = TargetPlatformDetector.getPlatform(originalFile).isJvm()
|
val isJvmModule = TargetPlatformDetector.getPlatform(originalFile).isJvm()
|
||||||
|
val project = originalFile.project
|
||||||
|
|
||||||
recordOriginalFile(parameters)
|
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 reference = (parameters.position.parent as? KtSimpleNameExpression)?.mainReference ?: return
|
||||||
val nameExpression = reference.expression.takeIf { it !is KtLabelReferenceExpression } ?: return
|
val nameExpression = reference.expression.takeIf { it !is KtLabelReferenceExpression } ?: return
|
||||||
|
|||||||
+116
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -48,6 +48,7 @@ open class KeywordLookupObject {
|
|||||||
override fun hashCode(): Int = javaClass.hashCode()
|
override fun hashCode(): Int = javaClass.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class KeywordCompletion(private val languageVersionSettingProvider: LanguageVersionSettingProvider) {
|
class KeywordCompletion(private val languageVersionSettingProvider: LanguageVersionSettingProvider) {
|
||||||
interface LanguageVersionSettingProvider {
|
interface LanguageVersionSettingProvider {
|
||||||
fun getLanguageVersionSetting(element: PsiElement): LanguageVersionSettings
|
fun getLanguageVersionSetting(element: PsiElement): LanguageVersionSettings
|
||||||
|
|||||||
+58
@@ -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 ""
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user