Move keyword completion stuff to frontend-independent module

This commit is contained in:
Ilya Kirillov
2021-05-04 16:42:24 +02:00
committed by TeamCityServer
parent d7a91cb05e
commit 62fe3930ff
8 changed files with 130 additions and 102 deletions
@@ -13,12 +13,14 @@ import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.codeInsight.lookup.LookupElementDecorator import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.codeInsight.lookup.LookupElementPresentation import com.intellij.codeInsight.lookup.LookupElementPresentation
import com.intellij.codeInsight.template.TemplateManager import com.intellij.codeInsight.template.TemplateManager
import com.intellij.openapi.module.Module
import com.intellij.patterns.PatternCondition import com.intellij.patterns.PatternCondition
import com.intellij.patterns.StandardPatterns import com.intellij.patterns.StandardPatterns
import com.intellij.psi.* 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
@@ -33,6 +35,7 @@ import org.jetbrains.kotlin.idea.core.NotPropertiesService
import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject
import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference.ShorteningMode.FORCED_SHORTENING import org.jetbrains.kotlin.idea.references.KtSimpleNameReference.ShorteningMode.FORCED_SHORTENING
import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil
@@ -539,6 +542,11 @@ class BasicCompletionSession(
override val descriptorKindFilter: DescriptorKindFilter? override val descriptorKindFilter: DescriptorKindFilter?
get() = null get() = null
private val keywordCompletion = KeywordCompletion(object : KeywordCompletion.LanguageVersionSettingProvider {
override fun getLanguageVersionSetting(element: PsiElement) = element.languageVersionSettings
override fun getLanguageVersionSetting(module: Module) = module.languageVersionSettings
})
override fun doComplete() { override fun doComplete() {
val keywordsToSkip = HashSet<String>() val keywordsToSkip = HashSet<String>()
@@ -574,7 +582,7 @@ 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 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
@@ -0,0 +1,25 @@
/*
* 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.handlers
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.Lookup
import com.intellij.codeInsight.lookup.LookupElement
import org.jetbrains.kotlin.idea.completion.KEEP_OLD_ARGUMENT_LIST_ON_TAB_KEY
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletion
import org.jetbrains.kotlin.idea.completion.tryGetOffset
class SmartCompletionTailOffsetProviderFE10Impl: SmartCompletionTailOffsetProvider() {
override fun getTailOffset(context: InsertionContext, item: LookupElement): Int {
val completionChar = context.completionChar
var tailOffset = context.tailOffset
if (completionChar == Lookup.REPLACE_SELECT_CHAR && item.getUserData(KEEP_OLD_ARGUMENT_LIST_ON_TAB_KEY) != null) {
context.offsetMap.tryGetOffset(SmartCompletion.OLD_ARGUMENTS_REPLACEMENT_OFFSET)
?.let { tailOffset = it }
}
return tailOffset
}
}
@@ -1,17 +1,6 @@
/* /*
* Copyright 2010-2015 JetBrains s.r.o. * 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.
* 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 package org.jetbrains.kotlin.idea.completion
@@ -21,6 +10,7 @@ import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.completion.PrefixMatcher import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtilCore import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.* import com.intellij.psi.*
@@ -37,7 +27,6 @@ import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget.* import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget.*
import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler
import org.jetbrains.kotlin.idea.completion.handlers.createKeywordConstructLookupElement import org.jetbrains.kotlin.idea.completion.handlers.createKeywordConstructLookupElement
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.lexer.KtKeywordToken import org.jetbrains.kotlin.lexer.KtKeywordToken
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens.* import org.jetbrains.kotlin.lexer.KtTokens.*
@@ -59,70 +48,77 @@ open class KeywordLookupObject {
override fun hashCode(): Int = javaClass.hashCode() override fun hashCode(): Int = javaClass.hashCode()
} }
object KeywordCompletion { class KeywordCompletion(private val languageVersionSettingProvider: LanguageVersionSettingProvider) {
private val ALL_KEYWORDS = (KEYWORDS.types + SOFT_KEYWORDS.types) interface LanguageVersionSettingProvider {
.map { it as KtKeywordToken } fun getLanguageVersionSetting(element: PsiElement): LanguageVersionSettings
fun getLanguageVersionSetting(module: Module): LanguageVersionSettings
}
private val KEYWORDS_TO_IGNORE_PREFIX = companion object {
TokenSet.create(OVERRIDE_KEYWORD /* it's needed to complete overrides that should be work by member name too */) private val ALL_KEYWORDS = (KEYWORDS.types + SOFT_KEYWORDS.types)
.map { it as KtKeywordToken }
private val INCOMPATIBLE_KEYWORDS_AROUND_SEALED = setOf( private val KEYWORDS_TO_IGNORE_PREFIX =
SEALED_KEYWORD, TokenSet.create(OVERRIDE_KEYWORD /* it's needed to complete overrides that should be work by member name too */)
ANNOTATION_KEYWORD,
DATA_KEYWORD,
ENUM_KEYWORD,
OPEN_KEYWORD,
INNER_KEYWORD,
ABSTRACT_KEYWORD
).mapTo(HashSet()) { it.value }
private val COMPOUND_KEYWORDS = mapOf<KtKeywordToken, Set<KtKeywordToken>>( private val INCOMPATIBLE_KEYWORDS_AROUND_SEALED = setOf(
COMPANION_KEYWORD to setOf(OBJECT_KEYWORD), SEALED_KEYWORD,
DATA_KEYWORD to setOf(CLASS_KEYWORD), ANNOTATION_KEYWORD,
ENUM_KEYWORD to setOf(CLASS_KEYWORD), DATA_KEYWORD,
ANNOTATION_KEYWORD to setOf(CLASS_KEYWORD), ENUM_KEYWORD,
SEALED_KEYWORD to setOf(CLASS_KEYWORD, INTERFACE_KEYWORD, FUN_KEYWORD), OPEN_KEYWORD,
LATEINIT_KEYWORD to setOf(VAR_KEYWORD), INNER_KEYWORD,
CONST_KEYWORD to setOf(VAL_KEYWORD), ABSTRACT_KEYWORD
SUSPEND_KEYWORD to setOf(FUN_KEYWORD) ).mapTo(HashSet()) { it.value }
)
private val COMPOUND_KEYWORDS_NOT_SUGGEST_TOGETHER = mapOf<KtKeywordToken, Set<KtKeywordToken>>( private val COMPOUND_KEYWORDS = mapOf<KtKeywordToken, Set<KtKeywordToken>>(
// 'fun' can follow 'sealed', e.g. "sealed fun interface". But "sealed fun" looks irrelevant differ to "sealed interface/class". COMPANION_KEYWORD to setOf(OBJECT_KEYWORD),
SEALED_KEYWORD to setOf(FUN_KEYWORD), DATA_KEYWORD to setOf(CLASS_KEYWORD),
) ENUM_KEYWORD to setOf(CLASS_KEYWORD),
ANNOTATION_KEYWORD to setOf(CLASS_KEYWORD),
SEALED_KEYWORD to setOf(CLASS_KEYWORD, INTERFACE_KEYWORD, FUN_KEYWORD),
LATEINIT_KEYWORD to setOf(VAR_KEYWORD),
CONST_KEYWORD to setOf(VAL_KEYWORD),
SUSPEND_KEYWORD to setOf(FUN_KEYWORD)
)
private val KEYWORD_CONSTRUCTS = mapOf<KtKeywordToken, String>( private val COMPOUND_KEYWORDS_NOT_SUGGEST_TOGETHER = mapOf<KtKeywordToken, Set<KtKeywordToken>>(
IF_KEYWORD to "fun foo() { if (caret)", // 'fun' can follow 'sealed', e.g. "sealed fun interface". But "sealed fun" looks irrelevant differ to "sealed interface/class".
WHILE_KEYWORD to "fun foo() { while(caret)", SEALED_KEYWORD to setOf(FUN_KEYWORD),
FOR_KEYWORD to "fun foo() { for(caret)", )
TRY_KEYWORD to "fun foo() { try {\ncaret\n}",
CATCH_KEYWORD to "fun foo() { try {} catch (caret)",
FINALLY_KEYWORD to "fun foo() { try {\n}\nfinally{\ncaret\n}",
DO_KEYWORD to "fun foo() { do {\ncaret\n}",
INIT_KEYWORD to "class C { init {\ncaret\n}",
CONSTRUCTOR_KEYWORD to "class C { constructor(caret)"
)
private val NO_SPACE_AFTER = listOf( private val KEYWORD_CONSTRUCTS = mapOf<KtKeywordToken, String>(
THIS_KEYWORD, IF_KEYWORD to "fun foo() { if (caret)",
SUPER_KEYWORD, WHILE_KEYWORD to "fun foo() { while(caret)",
NULL_KEYWORD, FOR_KEYWORD to "fun foo() { for(caret)",
TRUE_KEYWORD, TRY_KEYWORD to "fun foo() { try {\ncaret\n}",
FALSE_KEYWORD, CATCH_KEYWORD to "fun foo() { try {} catch (caret)",
BREAK_KEYWORD, FINALLY_KEYWORD to "fun foo() { try {\n}\nfinally{\ncaret\n}",
CONTINUE_KEYWORD, DO_KEYWORD to "fun foo() { do {\ncaret\n}",
ELSE_KEYWORD, INIT_KEYWORD to "class C { init {\ncaret\n}",
WHEN_KEYWORD, CONSTRUCTOR_KEYWORD to "class C { constructor(caret)"
FILE_KEYWORD, )
DYNAMIC_KEYWORD,
GET_KEYWORD, private val NO_SPACE_AFTER = listOf(
SET_KEYWORD THIS_KEYWORD,
).map { it.value } + "companion object" SUPER_KEYWORD,
NULL_KEYWORD,
TRUE_KEYWORD,
FALSE_KEYWORD,
BREAK_KEYWORD,
CONTINUE_KEYWORD,
ELSE_KEYWORD,
WHEN_KEYWORD,
FILE_KEYWORD,
DYNAMIC_KEYWORD,
GET_KEYWORD,
SET_KEYWORD
).map { it.value } + "companion object"
}
fun complete(position: PsiElement, prefixMatcher: PrefixMatcher, isJvmModule: Boolean, consumer: (LookupElement) -> Unit) { fun complete(position: PsiElement, prefixMatcher: PrefixMatcher, isJvmModule: Boolean, consumer: (LookupElement) -> Unit) {
if (!GENERAL_FILTER.isAcceptable(position, position)) return if (!GENERAL_FILTER.isAcceptable(position, position)) return
val sealedInterfacesEnabled = position.languageVersionSettings.supportsFeature(LanguageFeature.SealedInterfaces) val sealedInterfacesEnabled = languageVersionSettingProvider.getLanguageVersionSetting(position).supportsFeature(LanguageFeature.SealedInterfaces)
val parserFilter = buildFilter(position) val parserFilter = buildFilter(position)
for (keywordToken in ALL_KEYWORDS) { for (keywordToken in ALL_KEYWORDS) {
@@ -422,9 +418,9 @@ object KeywordCompletion {
fun isKeywordCorrectlyApplied(keywordTokenType: KtKeywordToken, file: KtFile): Boolean { fun isKeywordCorrectlyApplied(keywordTokenType: KtKeywordToken, file: KtFile): Boolean {
val elementAt = file.findElementAt(prefixText.length)!! val elementAt = file.findElementAt(prefixText.length)!!
val languageVersionSettings = ModuleUtilCore.findModuleForPsiElement(position)?.languageVersionSettings val languageVersionSettings =
?: LanguageVersionSettingsImpl.DEFAULT ModuleUtilCore.findModuleForPsiElement(position)?.let(languageVersionSettingProvider::getLanguageVersionSetting)
?: LanguageVersionSettingsImpl.DEFAULT
when { when {
!elementAt.node!!.elementType.matchesKeyword(keywordTokenType) -> return false !elementAt.node!!.elementType.matchesKeyword(keywordTokenType) -> return false
@@ -1,17 +1,6 @@
/* /*
* Copyright 2010-2015 JetBrains s.r.o. * 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.
* 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 package org.jetbrains.kotlin.idea.completion.handlers
@@ -19,13 +8,15 @@ package org.jetbrains.kotlin.idea.completion.handlers
import com.intellij.codeInsight.AutoPopupController import com.intellij.codeInsight.AutoPopupController
import com.intellij.codeInsight.completion.InsertHandler import com.intellij.codeInsight.completion.InsertHandler
import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.Lookup
import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.openapi.components.serviceOrNull
import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Document
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.idea.completion.KEEP_OLD_ARGUMENT_LIST_ON_TAB_KEY
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletion
import org.jetbrains.kotlin.idea.completion.tryGetOffset abstract class SmartCompletionTailOffsetProvider {
abstract fun getTailOffset(context: InsertionContext, item: LookupElement): Int
}
class WithTailInsertHandler( class WithTailInsertHandler(
val tailText: String, val tailText: String,
@@ -48,11 +39,8 @@ class WithTailInsertHandler(
val document = context.document val document = context.document
PsiDocumentManager.getInstance(context.project).doPostponedOperationsAndUnblockDocument(document) PsiDocumentManager.getInstance(context.project).doPostponedOperationsAndUnblockDocument(document)
var tailOffset = context.tailOffset var tailOffset = serviceOrNull<SmartCompletionTailOffsetProvider>()?.getTailOffset(context, item)
if (completionChar == Lookup.REPLACE_SELECT_CHAR && item.getUserData(KEEP_OLD_ARGUMENT_LIST_ON_TAB_KEY) != null) { ?:context.tailOffset
context.offsetMap.tryGetOffset(SmartCompletion.OLD_ARGUMENTS_REPLACEMENT_OFFSET)
?.let { tailOffset = it }
}
val moveCaret = context.editor.caretModel.offset == tailOffset val moveCaret = context.editor.caretModel.offset == tailOffset
@@ -150,11 +150,6 @@ fun Editor.unblockDocument() {
} }
} }
fun Editor.moveCaret(offset: Int, scrollType: ScrollType = ScrollType.RELATIVE) {
caretModel.moveToOffset(offset)
scrollingModel.scrollToCaret(scrollType)
}
private fun findInsertAfterAnchor(editor: Editor?, body: KtClassBody): PsiElement? { private fun findInsertAfterAnchor(editor: Editor?, body: KtClassBody): PsiElement? {
val afterAnchor = body.lBrace ?: return null val afterAnchor = body.lBrace ?: return null
@@ -0,0 +1,15 @@
/*
* 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.core
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ScrollType
fun Editor.moveCaret(offset: Int, scrollType: ScrollType = ScrollType.RELATIVE) {
caretModel.moveToOffset(offset)
scrollingModel.scrollToCaret(scrollType)
}
@@ -148,7 +148,8 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
<extensions defaultExtensionNs="com.intellij"> <extensions defaultExtensionNs="com.intellij">
<pathMacroContributor implementation="org.jetbrains.kotlin.idea.KotlinPluginMacros"/> <pathMacroContributor implementation="org.jetbrains.kotlin.idea.KotlinPluginMacros"/>
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupApplicationService" /> <applicationService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupApplicationService" />
<applicationService serviceInterface="org.jetbrains.kotlin.idea.completion.handlers.SmartCompletionTailOffsetProvider"
serviceImplementation="org.jetbrains.kotlin.idea.completion.handlers.SmartCompletionTailOffsetProviderFE10Impl"/>
<postStartupActivity implementation="org.jetbrains.kotlin.idea.PluginStartupActivity"/> <postStartupActivity implementation="org.jetbrains.kotlin.idea.PluginStartupActivity"/>
<projectService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupService"/> <projectService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupService"/>