diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt index 11abbe4de65..fa799b10972 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt @@ -66,15 +66,15 @@ object KeywordCompletion { 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 OBJECT_KEYWORD, - DATA_KEYWORD to CLASS_KEYWORD, - ENUM_KEYWORD to CLASS_KEYWORD, - ANNOTATION_KEYWORD to CLASS_KEYWORD, - SEALED_KEYWORD to CLASS_KEYWORD, - LATEINIT_KEYWORD to VAR_KEYWORD, - CONST_KEYWORD to VAL_KEYWORD, - SUSPEND_KEYWORD to 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), + LATEINIT_KEYWORD to setOf(VAR_KEYWORD), + CONST_KEYWORD to setOf(VAL_KEYWORD), + SUSPEND_KEYWORD to setOf(FUN_KEYWORD) ) private val KEYWORD_CONSTRUCTS = mapOf( @@ -105,54 +105,73 @@ object KeywordCompletion { 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 parserFilter = buildFilter(position) for (keywordToken in ALL_KEYWORDS) { - var keyword = keywordToken.value - - val nextKeyword = when { - keywordToken == SUSPEND_KEYWORD && position.getStrictParentOfType() != null -> null - else -> COMPOUND_KEYWORDS[keywordToken] + val nextKeywords = keywordToken.getNextPossibleKeywords(position) ?: setOf(null) + nextKeywords.forEach { + handleCompoundKeyword(position, keywordToken, it, isJvmModule, prefixMatcher, parserFilter, consumer) } - var applicableAsCompound = false - if (nextKeyword != null) { - fun PsiElement.isSpace() = this is PsiWhiteSpace && '\n' !in getText() + } + } - var next = position.nextLeaf { !(it.isSpace() || it.text == "$") }?.text - if (next != null && next.startsWith("$")) { - next = next.substring(1) - } - if (next != nextKeyword.value) - keyword += " " + nextKeyword.value - else - applicableAsCompound = true - } + private fun KtKeywordToken.getNextPossibleKeywords(position: PsiElement): Set? { + return when { + this == SUSPEND_KEYWORD && position.getStrictParentOfType() != null -> null + else -> COMPOUND_KEYWORDS[this] + } + } - if (keywordToken == DYNAMIC_KEYWORD && isJvmModule) continue // not supported for JVM + private fun handleCompoundKeyword( + position: PsiElement, + keywordToken: KtKeywordToken, + nextKeyword: KtKeywordToken?, + isJvmModule: Boolean, + prefixMatcher: PrefixMatcher, + parserFilter: (KtKeywordToken) -> Boolean, + consumer: (LookupElement) -> Unit + ) { + var keyword = keywordToken.value - if (keywordToken !in KEYWORDS_TO_IGNORE_PREFIX && !prefixMatcher.isStartMatch(keyword)) continue + var applicableAsCompound = false + if (nextKeyword != null) { + fun PsiElement.isSpace() = this is PsiWhiteSpace && '\n' !in getText() - if (!parserFilter(keywordToken)) continue + var next = position.nextLeaf { !(it.isSpace() || it.text == "$") }?.text + next = next?.removePrefix("$") - val constructText = KEYWORD_CONSTRUCTS[keywordToken] - if (constructText != null && !applicableAsCompound) { - val element = createKeywordConstructLookupElement(position.project, keyword, constructText) - consumer(element) - } else { - if (listOf(CLASS_KEYWORD, OBJECT_KEYWORD, INTERFACE_KEYWORD).any { keyword.endsWith(it.value) }) { - val topLevelClassName = getTopLevelClassName(position) - if (topLevelClassName != null) { - if (keyword.startsWith(DATA_KEYWORD.value)) { - consumer(createKeywordConstructLookupElement(position.project, keyword, "$keyword $topLevelClassName(caret)")) - } else { - consumer(createLookupElementBuilder("$keyword $topLevelClassName", position)) - } + val nextIsNotYetPresent = keywordToken.getNextPossibleKeywords(position)?.none { it.value == next } == true + if (nextIsNotYetPresent) + keyword += " " + nextKeyword.value + else + applicableAsCompound = true + } + + if (keywordToken == DYNAMIC_KEYWORD && isJvmModule) return // not supported for JVM + + if (keywordToken !in KEYWORDS_TO_IGNORE_PREFIX && !prefixMatcher.isStartMatch(keyword)) return + + if (!parserFilter(keywordToken)) return + + val constructText = KEYWORD_CONSTRUCTS[keywordToken] + if (constructText != null && !applicableAsCompound) { + val element = createKeywordConstructLookupElement(position.project, keyword, constructText) + consumer(element) + } else { + if (listOf(CLASS_KEYWORD, OBJECT_KEYWORD, INTERFACE_KEYWORD).any { keyword.endsWith(it.value) }) { + val topLevelClassName = getTopLevelClassName(position) + if (topLevelClassName != null) { + if (keyword.startsWith(DATA_KEYWORD.value)) { + consumer(createKeywordConstructLookupElement(position.project, keyword, "$keyword $topLevelClassName(caret)")) + } else { + consumer(createLookupElementBuilder("$keyword $topLevelClassName", position)) } } - consumer(createLookupElementBuilder(keyword, position)) } + consumer(createLookupElementBuilder(keyword, position)) } } diff --git a/idea/idea-completion/testData/keywords/AfterClassProperty.kt b/idea/idea-completion/testData/keywords/AfterClassProperty.kt index eda4112e774..788cee4e10a 100644 --- a/idea/idea-completion/testData/keywords/AfterClassProperty.kt +++ b/idea/idea-completion/testData/keywords/AfterClassProperty.kt @@ -33,6 +33,7 @@ class MouseMovedEventArgs // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed interface // EXIST: lateinit var // EXIST: data class // EXIST: inline diff --git a/idea/idea-completion/testData/keywords/AfterClasses.kt b/idea/idea-completion/testData/keywords/AfterClasses.kt index 2aaaaf6a926..6d16b266393 100644 --- a/idea/idea-completion/testData/keywords/AfterClasses.kt +++ b/idea/idea-completion/testData/keywords/AfterClasses.kt @@ -31,6 +31,7 @@ class AfterClasses { // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed interface // EXIST: data class // EXIST: inline // EXIST: value diff --git a/idea/idea-completion/testData/keywords/AfterClasses_LangLevel10.kt b/idea/idea-completion/testData/keywords/AfterClasses_LangLevel10.kt index 17727796838..d6dd4d72438 100644 --- a/idea/idea-completion/testData/keywords/AfterClasses_LangLevel10.kt +++ b/idea/idea-completion/testData/keywords/AfterClasses_LangLevel10.kt @@ -33,6 +33,8 @@ class B { // EXIST: infix // EXIST: sealed class // EXIST: sealed class AfterClasses_LangLevel10 +// EXIST: sealed interface AfterClasses_LangLevel10 +// EXIST: sealed interface // EXIST: data class // EXIST: { "lookupString":"data class", "itemText":"data class", "tailText":" AfterClasses_LangLevel10(...)", "attributes":"bold" } // EXIST: inline diff --git a/idea/idea-completion/testData/keywords/AfterClasses_LangLevel11.kt b/idea/idea-completion/testData/keywords/AfterClasses_LangLevel11.kt index 24bbfe25aad..7263e242a82 100644 --- a/idea/idea-completion/testData/keywords/AfterClasses_LangLevel11.kt +++ b/idea/idea-completion/testData/keywords/AfterClasses_LangLevel11.kt @@ -33,6 +33,8 @@ class B { // EXIST: infix // EXIST: sealed class // EXIST: sealed class AfterClasses_LangLevel11 +// EXIST: sealed interface AfterClasses_LangLevel11 +// EXIST: sealed interface // EXIST: data class // EXIST: { "lookupString":"data class", "itemText":"data class", "tailText":" AfterClasses_LangLevel11(...)", "attributes":"bold" } // EXIST: inline diff --git a/idea/idea-completion/testData/keywords/AfterFuns.kt b/idea/idea-completion/testData/keywords/AfterFuns.kt index 54966f4890a..3f915220f47 100644 --- a/idea/idea-completion/testData/keywords/AfterFuns.kt +++ b/idea/idea-completion/testData/keywords/AfterFuns.kt @@ -32,6 +32,7 @@ class A { // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed interface // EXIST: lateinit var // EXIST: data class // EXIST: inline diff --git a/idea/idea-completion/testData/keywords/GlobalPropertyAccessors.kt b/idea/idea-completion/testData/keywords/GlobalPropertyAccessors.kt index 8220a22f884..ab7873fa77e 100644 --- a/idea/idea-completion/testData/keywords/GlobalPropertyAccessors.kt +++ b/idea/idea-completion/testData/keywords/GlobalPropertyAccessors.kt @@ -37,6 +37,8 @@ var a : Int // EXIST: infix // EXIST: sealed class // EXIST: sealed class GlobalPropertyAccessors +// EXIST: sealed interface GlobalPropertyAccessors +// EXIST: sealed interface // EXIST: data class // EXIST: { "lookupString":"data class", "itemText":"data class", "tailText":" GlobalPropertyAccessors(...)", "attributes":"bold" } // EXIST: inline diff --git a/idea/idea-completion/testData/keywords/InAnnotationClassScope.kt b/idea/idea-completion/testData/keywords/InAnnotationClassScope.kt index 834ffb8f046..cc49effaff9 100644 --- a/idea/idea-completion/testData/keywords/InAnnotationClassScope.kt +++ b/idea/idea-completion/testData/keywords/InAnnotationClassScope.kt @@ -18,6 +18,7 @@ annotation class Test { // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed interface // EXIST: lateinit var // EXIST: data class // EXIST: inline diff --git a/idea/idea-completion/testData/keywords/InClassBeforeFun.kt b/idea/idea-completion/testData/keywords/InClassBeforeFun.kt index 44b688bde61..ebc5751c42d 100644 --- a/idea/idea-completion/testData/keywords/InClassBeforeFun.kt +++ b/idea/idea-completion/testData/keywords/InClassBeforeFun.kt @@ -30,6 +30,7 @@ public class Test { // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed interface // EXIST: lateinit var // EXIST: data class // EXIST: inline diff --git a/idea/idea-completion/testData/keywords/InClassScope.kt b/idea/idea-completion/testData/keywords/InClassScope.kt index a8f87a62b64..de5a03acfe6 100644 --- a/idea/idea-completion/testData/keywords/InClassScope.kt +++ b/idea/idea-completion/testData/keywords/InClassScope.kt @@ -24,6 +24,7 @@ class TestClass { // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed interface // EXIST: lateinit var // EXIST: data class // EXIST: inline diff --git a/idea/idea-completion/testData/keywords/InEnumScope2.kt b/idea/idea-completion/testData/keywords/InEnumScope2.kt index f8e87c232c5..2251838ea0f 100644 --- a/idea/idea-completion/testData/keywords/InEnumScope2.kt +++ b/idea/idea-completion/testData/keywords/InEnumScope2.kt @@ -18,6 +18,7 @@ enum class Test { // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed interface // EXIST: lateinit var // EXIST: data class // EXIST: inline diff --git a/idea/idea-completion/testData/keywords/InInterfaceScope.kt b/idea/idea-completion/testData/keywords/InInterfaceScope.kt index 230e1573307..11689d4e8de 100644 --- a/idea/idea-completion/testData/keywords/InInterfaceScope.kt +++ b/idea/idea-completion/testData/keywords/InInterfaceScope.kt @@ -20,6 +20,7 @@ interface Test { // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed interface // EXIST: lateinit var // EXIST: data class // EXIST: inline diff --git a/idea/idea-completion/testData/keywords/InObjectScope.kt b/idea/idea-completion/testData/keywords/InObjectScope.kt index b2c7f817171..8f8fc86717b 100644 --- a/idea/idea-completion/testData/keywords/InObjectScope.kt +++ b/idea/idea-completion/testData/keywords/InObjectScope.kt @@ -21,6 +21,7 @@ object Test { // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed interface // EXIST: lateinit var // EXIST: data class // EXIST: inline diff --git a/idea/idea-completion/testData/keywords/InTopScopeAfterPackage.kt b/idea/idea-completion/testData/keywords/InTopScopeAfterPackage.kt index f6eb462e225..282b0c8d8df 100644 --- a/idea/idea-completion/testData/keywords/InTopScopeAfterPackage.kt +++ b/idea/idea-completion/testData/keywords/InTopScopeAfterPackage.kt @@ -24,6 +24,8 @@ package Test // EXIST: infix // EXIST: sealed class // EXIST: sealed class InTopScopeAfterPackage +// EXIST: sealed interface InTopScopeAfterPackage +// EXIST: sealed interface // EXIST: data class // EXIST: { "lookupString":"data class", "itemText":"data class", "tailText":" InTopScopeAfterPackage(...)", "attributes":"bold" } // EXIST: inline diff --git a/idea/idea-completion/testData/keywords/PropertyAccessors.kt b/idea/idea-completion/testData/keywords/PropertyAccessors.kt index b662fdf7444..8a6e6ace135 100644 --- a/idea/idea-completion/testData/keywords/PropertyAccessors.kt +++ b/idea/idea-completion/testData/keywords/PropertyAccessors.kt @@ -32,6 +32,7 @@ class Some { // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed interface // EXIST: lateinit var // EXIST: data class // EXIST: inline diff --git a/idea/idea-completion/testData/keywords/PropertyAccessors2.kt b/idea/idea-completion/testData/keywords/PropertyAccessors2.kt index f4326fbd54b..269f3f45e15 100644 --- a/idea/idea-completion/testData/keywords/PropertyAccessors2.kt +++ b/idea/idea-completion/testData/keywords/PropertyAccessors2.kt @@ -32,6 +32,7 @@ class Some { // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed interface // EXIST: lateinit var // EXIST: data class // EXIST: inline diff --git a/idea/idea-completion/testData/keywords/PropertySetter.kt b/idea/idea-completion/testData/keywords/PropertySetter.kt index 491c24fe4c1..f950db00539 100644 --- a/idea/idea-completion/testData/keywords/PropertySetter.kt +++ b/idea/idea-completion/testData/keywords/PropertySetter.kt @@ -30,6 +30,7 @@ class Some { // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed interface // EXIST: lateinit var // EXIST: data class // EXIST: inline diff --git a/idea/idea-completion/testData/keywords/SealedForDeclaredClass.kt b/idea/idea-completion/testData/keywords/SealedForDeclaredClass.kt new file mode 100644 index 00000000000..f12d91839e1 --- /dev/null +++ b/idea/idea-completion/testData/keywords/SealedForDeclaredClass.kt @@ -0,0 +1,3 @@ +sealclass A +// EXIST: "sealed" +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/keywords/SealedForDeclaredInterface.kt b/idea/idea-completion/testData/keywords/SealedForDeclaredInterface.kt new file mode 100644 index 00000000000..c1a96d3b410 --- /dev/null +++ b/idea/idea-completion/testData/keywords/SealedForDeclaredInterface.kt @@ -0,0 +1,3 @@ +sealinterface A +// EXIST: "sealed" +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/keywords/SealedWithName.kt b/idea/idea-completion/testData/keywords/SealedWithName.kt new file mode 100644 index 00000000000..345a354e786 --- /dev/null +++ b/idea/idea-completion/testData/keywords/SealedWithName.kt @@ -0,0 +1,6 @@ +seal +// EXIST: "sealed class SealedWithName" +// EXIST: "sealed interface SealedWithName" +// EXIST: "sealed class" +// EXIST: "sealed interface" +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/keywords/SealedWithoutName.kt b/idea/idea-completion/testData/keywords/SealedWithoutName.kt new file mode 100644 index 00000000000..de4abff9998 --- /dev/null +++ b/idea/idea-completion/testData/keywords/SealedWithoutName.kt @@ -0,0 +1,6 @@ +class OuterClass { + seal +} +// EXIST: "sealed class" +// EXIST: "sealed interface" +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/keywords/TopScope.kt b/idea/idea-completion/testData/keywords/TopScope.kt index a0047259892..47b03855278 100644 --- a/idea/idea-completion/testData/keywords/TopScope.kt +++ b/idea/idea-completion/testData/keywords/TopScope.kt @@ -23,6 +23,8 @@ // EXIST: infix // EXIST: sealed class // EXIST: sealed class TopScope +// EXIST: sealed interface TopScope +// EXIST: sealed interface // EXIST: data class // EXIST: { "lookupString":"data class", "itemText":"data class", "tailText":" TopScope(...)", "attributes":"bold" } // EXIST: inline diff --git a/idea/idea-completion/testData/keywords/TopScope3-.kt b/idea/idea-completion/testData/keywords/TopScope3-.kt index dda4333afe5..63e8eaf6465 100644 --- a/idea/idea-completion/testData/keywords/TopScope3-.kt +++ b/idea/idea-completion/testData/keywords/TopScope3-.kt @@ -18,6 +18,7 @@ // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed interface // EXIST: data class // EXIST: inline // EXIST: value diff --git a/idea/idea-completion/testData/keywords/topScope2.kt b/idea/idea-completion/testData/keywords/topScope2.kt index dda4333afe5..63e8eaf6465 100644 --- a/idea/idea-completion/testData/keywords/topScope2.kt +++ b/idea/idea-completion/testData/keywords/topScope2.kt @@ -18,6 +18,7 @@ // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed interface // EXIST: data class // EXIST: inline // EXIST: value diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/KeywordCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/KeywordCompletionTestGenerated.java index 9c58628cac0..4f596d121ef 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/KeywordCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/KeywordCompletionTestGenerated.java @@ -503,6 +503,26 @@ public class KeywordCompletionTestGenerated extends AbstractKeywordCompletionTes runTest("idea/idea-completion/testData/keywords/ReturnSet.kt"); } + @TestMetadata("SealedForDeclaredClass.kt") + public void testSealedForDeclaredClass() throws Exception { + runTest("idea/idea-completion/testData/keywords/SealedForDeclaredClass.kt"); + } + + @TestMetadata("SealedForDeclaredInterface.kt") + public void testSealedForDeclaredInterface() throws Exception { + runTest("idea/idea-completion/testData/keywords/SealedForDeclaredInterface.kt"); + } + + @TestMetadata("SealedWithName.kt") + public void testSealedWithName() throws Exception { + runTest("idea/idea-completion/testData/keywords/SealedWithName.kt"); + } + + @TestMetadata("SealedWithoutName.kt") + public void testSealedWithoutName() throws Exception { + runTest("idea/idea-completion/testData/keywords/SealedWithoutName.kt"); + } + @TestMetadata("SuspendInParameterTypePosition.kt") public void testSuspendInParameterTypePosition() throws Exception { runTest("idea/idea-completion/testData/keywords/SuspendInParameterTypePosition.kt");