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.LookupElementPresentation
import com.intellij.codeInsight.template.TemplateManager
import com.intellij.openapi.module.Module
import com.intellij.patterns.PatternCondition
import com.intellij.patterns.StandardPatterns
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
@@ -33,6 +35,7 @@ import org.jetbrains.kotlin.idea.core.NotPropertiesService
import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject
import org.jetbrains.kotlin.idea.imports.importableFqName
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.mainReference
import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil
@@ -539,6 +542,11 @@ class BasicCompletionSession(
override val descriptorKindFilter: DescriptorKindFilter?
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() {
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 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
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.
*
* 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.
* 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
@@ -21,6 +10,7 @@ import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.util.io.FileUtil
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.idea.completion.handlers.WithTailInsertHandler
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.KtModifierKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens.*
@@ -59,70 +48,77 @@ open class KeywordLookupObject {
override fun hashCode(): Int = javaClass.hashCode()
}
object KeywordCompletion {
private val ALL_KEYWORDS = (KEYWORDS.types + SOFT_KEYWORDS.types)
.map { it as KtKeywordToken }
class KeywordCompletion(private val languageVersionSettingProvider: LanguageVersionSettingProvider) {
interface LanguageVersionSettingProvider {
fun getLanguageVersionSetting(element: PsiElement): LanguageVersionSettings
fun getLanguageVersionSetting(module: Module): LanguageVersionSettings
}
private val KEYWORDS_TO_IGNORE_PREFIX =
TokenSet.create(OVERRIDE_KEYWORD /* it's needed to complete overrides that should be work by member name too */)
companion object {
private val ALL_KEYWORDS = (KEYWORDS.types + SOFT_KEYWORDS.types)
.map { it as KtKeywordToken }
private val INCOMPATIBLE_KEYWORDS_AROUND_SEALED = setOf(
SEALED_KEYWORD,
ANNOTATION_KEYWORD,
DATA_KEYWORD,
ENUM_KEYWORD,
OPEN_KEYWORD,
INNER_KEYWORD,
ABSTRACT_KEYWORD
).mapTo(HashSet()) { it.value }
private val KEYWORDS_TO_IGNORE_PREFIX =
TokenSet.create(OVERRIDE_KEYWORD /* it's needed to complete overrides that should be work by member name too */)
private val COMPOUND_KEYWORDS = mapOf<KtKeywordToken, Set<KtKeywordToken>>(
COMPANION_KEYWORD to setOf(OBJECT_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 INCOMPATIBLE_KEYWORDS_AROUND_SEALED = setOf(
SEALED_KEYWORD,
ANNOTATION_KEYWORD,
DATA_KEYWORD,
ENUM_KEYWORD,
OPEN_KEYWORD,
INNER_KEYWORD,
ABSTRACT_KEYWORD
).mapTo(HashSet()) { it.value }
private val COMPOUND_KEYWORDS_NOT_SUGGEST_TOGETHER = mapOf<KtKeywordToken, Set<KtKeywordToken>>(
// 'fun' can follow 'sealed', e.g. "sealed fun interface". But "sealed fun" looks irrelevant differ to "sealed interface/class".
SEALED_KEYWORD to setOf(FUN_KEYWORD),
)
private val COMPOUND_KEYWORDS = mapOf<KtKeywordToken, Set<KtKeywordToken>>(
COMPANION_KEYWORD to setOf(OBJECT_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>(
IF_KEYWORD to "fun foo() { if (caret)",
WHILE_KEYWORD to "fun foo() { while(caret)",
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 COMPOUND_KEYWORDS_NOT_SUGGEST_TOGETHER = mapOf<KtKeywordToken, Set<KtKeywordToken>>(
// 'fun' can follow 'sealed', e.g. "sealed fun interface". But "sealed fun" looks irrelevant differ to "sealed interface/class".
SEALED_KEYWORD to setOf(FUN_KEYWORD),
)
private val NO_SPACE_AFTER = listOf(
THIS_KEYWORD,
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"
private val KEYWORD_CONSTRUCTS = mapOf<KtKeywordToken, String>(
IF_KEYWORD to "fun foo() { if (caret)",
WHILE_KEYWORD to "fun foo() { while(caret)",
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(
THIS_KEYWORD,
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) {
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)
for (keywordToken in ALL_KEYWORDS) {
@@ -422,9 +418,9 @@ object KeywordCompletion {
fun isKeywordCorrectlyApplied(keywordTokenType: KtKeywordToken, file: KtFile): Boolean {
val elementAt = file.findElementAt(prefixText.length)!!
val languageVersionSettings = ModuleUtilCore.findModuleForPsiElement(position)?.languageVersionSettings
?: LanguageVersionSettingsImpl.DEFAULT
val languageVersionSettings =
ModuleUtilCore.findModuleForPsiElement(position)?.let(languageVersionSettingProvider::getLanguageVersionSetting)
?: LanguageVersionSettingsImpl.DEFAULT
when {
!elementAt.node!!.elementType.matchesKeyword(keywordTokenType) -> return false
@@ -1,17 +1,6 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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.
* 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
@@ -19,13 +8,15 @@ package org.jetbrains.kotlin.idea.completion.handlers
import com.intellij.codeInsight.AutoPopupController
import com.intellij.codeInsight.completion.InsertHandler
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.Lookup
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.openapi.components.serviceOrNull
import com.intellij.openapi.editor.Document
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(
val tailText: String,
@@ -48,11 +39,8 @@ class WithTailInsertHandler(
val document = context.document
PsiDocumentManager.getInstance(context.project).doPostponedOperationsAndUnblockDocument(document)
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 }
}
var tailOffset = serviceOrNull<SmartCompletionTailOffsetProvider>()?.getTailOffset(context, item)
?:context.tailOffset
val moveCaret = context.editor.caretModel.offset == tailOffset
@@ -107,4 +95,4 @@ class WithTailInsertHandler(
val EQ = WithTailInsertHandler("=", spaceBefore = true, spaceAfter = true) /*TODO: use code style options*/
val SPACE = WithTailInsertHandler(" ", spaceBefore = false, spaceAfter = false, overwriteText = true)
}
}
}
@@ -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? {
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">
<pathMacroContributor implementation="org.jetbrains.kotlin.idea.KotlinPluginMacros"/>
<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"/>
<projectService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupService"/>