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 08e3c3c0147..35d4323d628 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 @@ -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() @@ -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 diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/SmartCompletionTailOffsetProviderFE10Impl.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/SmartCompletionTailOffsetProviderFE10Impl.kt new file mode 100644 index 00000000000..981f57920da --- /dev/null +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/SmartCompletionTailOffsetProviderFE10Impl.kt @@ -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 + } +} \ No newline at end of file diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt similarity index 87% rename from idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt rename to idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt index 3e5c75b2491..5c58a31352f 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt @@ -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>( - 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>( - // '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>( + 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( - 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>( + // '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( + 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 diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/WithTailInsertHandler.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/handlers/WithTailInsertHandler.kt similarity index 75% rename from idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/WithTailInsertHandler.kt rename to idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/handlers/WithTailInsertHandler.kt index ad9bfc2a8a1..62404a7a69f 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/WithTailInsertHandler.kt +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/handlers/WithTailInsertHandler.kt @@ -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()?.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) } -} +} \ No newline at end of file diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/handlerUtils.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/handlers/handlerUtils.kt similarity index 100% rename from idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/handlerUtils.kt rename to idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/handlers/handlerUtils.kt diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/core/generateUtil.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/core/generateUtil.kt index 713bead0040..a80fbe32fce 100644 --- a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/core/generateUtil.kt +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/core/generateUtil.kt @@ -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 diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/core/generateUtils.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/core/generateUtils.kt new file mode 100644 index 00000000000..e4a74660f2f --- /dev/null +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/core/generateUtils.kt @@ -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) +} diff --git a/idea/resources-descriptors/META-INF/plugin.xml b/idea/resources-descriptors/META-INF/plugin.xml index 5a7971c1890..f5e9823bcb5 100644 --- a/idea/resources-descriptors/META-INF/plugin.xml +++ b/idea/resources-descriptors/META-INF/plugin.xml @@ -148,7 +148,8 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. - +