diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt index 35d4323d628..325f2ee3596 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt @@ -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) } } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt index cae21486dac..7399c147308 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt @@ -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 { - val result = ArrayList() - - 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 diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/FirKeywordCompletion.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/FirKeywordCompletion.kt index df8732afbfa..0ff00ad254c 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/FirKeywordCompletion.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/FirKeywordCompletion.kt @@ -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) } } } \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt index fd009f6d340..73d177c243c 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt @@ -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 diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/CompletionKeywordHandlers.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/CompletionKeywordHandlers.kt new file mode 100644 index 00000000000..c72c7bda8ab --- /dev/null +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/CompletionKeywordHandlers.kt @@ -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) { + 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 + + companion object { + inline operator fun invoke( + keyword: KtKeywordToken, + crossinline create: ( + parameters: CompletionParameters, + expression: KtExpression?, + lookup: LookupElement, + project: Project + ) -> Collection + ) = object : CompletionKeywordHandler(keyword) { + override fun createLookups( + parameters: CompletionParameters, + expression: KtExpression?, + lookup: LookupElement, + project: Project + ): Collection = create(parameters, expression, lookup, project) + } + } +} diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt index 5c58a31352f..7b53d746d19 100644 --- a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt @@ -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 diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/completionUtilsNoResolve.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/completionUtilsNoResolve.kt new file mode 100644 index 00000000000..ac4ed052987 --- /dev/null +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/completionUtilsNoResolve.kt @@ -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 { + val result = ArrayList() + + 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 "" + +