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 fa799b10972..9317092d4fe 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,17 +66,32 @@ 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 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 = 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), + 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 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 KEYWORD_CONSTRUCTS = mapOf( IF_KEYWORD to "fun foo() { if (caret)", WHILE_KEYWORD to "fun foo() { while(caret)", @@ -105,7 +120,6 @@ 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 @@ -125,6 +139,11 @@ object KeywordCompletion { } } + private fun KtKeywordToken.avoidSuggestingWith(keywordToken: KtKeywordToken): Boolean { + val nextKeywords = COMPOUND_KEYWORDS_NOT_SUGGEST_TOGETHER[this] ?: return false + return keywordToken in nextKeywords + } + private fun handleCompoundKeyword( position: PsiElement, keywordToken: KtKeywordToken, @@ -143,7 +162,15 @@ object KeywordCompletion { var next = position.nextLeaf { !(it.isSpace() || it.text == "$") }?.text next = next?.removePrefix("$") + if (keywordToken == SEALED_KEYWORD) { + if (next in INCOMPATIBLE_KEYWORDS_AROUND_SEALED) return + val prev = position.prevLeaf { !(it.isSpace() || it is PsiErrorElement) }?.text + if (prev in INCOMPATIBLE_KEYWORDS_AROUND_SEALED) return + } + val nextIsNotYetPresent = keywordToken.getNextPossibleKeywords(position)?.none { it.value == next } == true + if (nextIsNotYetPresent && keywordToken.avoidSuggestingWith(nextKeyword)) return + if (nextIsNotYetPresent) keyword += " " + nextKeyword.value else @@ -609,4 +636,4 @@ object KeywordCompletion { else -> it.parent } } -} +} \ No newline at end of file diff --git a/idea/idea-completion/testData/keywords/SealedForAlreadySealed.kt b/idea/idea-completion/testData/keywords/SealedForAlreadySealed.kt new file mode 100644 index 00000000000..7d8d077dcd3 --- /dev/null +++ b/idea/idea-completion/testData/keywords/SealedForAlreadySealed.kt @@ -0,0 +1,4 @@ +seal sealed class A +// ABSENT: "sealed" +// ABSENT: "sealed class" +// ABSENT: "sealed interface" \ No newline at end of file diff --git a/idea/idea-completion/testData/keywords/SealedForAnnotationClass.kt b/idea/idea-completion/testData/keywords/SealedForAnnotationClass.kt new file mode 100644 index 00000000000..7fe489d68bc --- /dev/null +++ b/idea/idea-completion/testData/keywords/SealedForAnnotationClass.kt @@ -0,0 +1,4 @@ +seal annotation class A +// ABSENT: "sealed" +// ABSENT: "sealed class" +// ABSENT: "sealed interface" \ No newline at end of file diff --git a/idea/idea-completion/testData/keywords/SealedForDataClass.kt b/idea/idea-completion/testData/keywords/SealedForDataClass.kt new file mode 100644 index 00000000000..08688c6481b --- /dev/null +++ b/idea/idea-completion/testData/keywords/SealedForDataClass.kt @@ -0,0 +1,2 @@ +seal data class A(val f: Int) +// ABSENT: "sealed" \ No newline at end of file diff --git a/idea/idea-completion/testData/keywords/SealedForEnumClass.kt b/idea/idea-completion/testData/keywords/SealedForEnumClass.kt new file mode 100644 index 00000000000..e4699e61300 --- /dev/null +++ b/idea/idea-completion/testData/keywords/SealedForEnumClass.kt @@ -0,0 +1,2 @@ +seal enum class A +// ABSENT: "sealed" \ No newline at end of file diff --git a/idea/idea-completion/testData/keywords/SealedForFunInterface.kt b/idea/idea-completion/testData/keywords/SealedForFunInterface.kt new file mode 100644 index 00000000000..5eabec43764 --- /dev/null +++ b/idea/idea-completion/testData/keywords/SealedForFunInterface.kt @@ -0,0 +1,6 @@ +seal fun interface A { + fun aFunction() +} + +// EXIST: "sealed" +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/keywords/SealedForInnerClass.kt b/idea/idea-completion/testData/keywords/SealedForInnerClass.kt new file mode 100644 index 00000000000..dcaf19f6cb6 --- /dev/null +++ b/idea/idea-completion/testData/keywords/SealedForInnerClass.kt @@ -0,0 +1,4 @@ +class A { + sealinner class B +} +// ABSENT: "sealed" \ No newline at end of file diff --git a/idea/idea-completion/testData/keywords/SealedForOpenClass.kt b/idea/idea-completion/testData/keywords/SealedForOpenClass.kt new file mode 100644 index 00000000000..3abc4394930 --- /dev/null +++ b/idea/idea-completion/testData/keywords/SealedForOpenClass.kt @@ -0,0 +1,2 @@ +seal open class A +// ABSENT: "sealed" \ No newline at end of file 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 4f596d121ef..98da39a1e69 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,21 @@ public class KeywordCompletionTestGenerated extends AbstractKeywordCompletionTes runTest("idea/idea-completion/testData/keywords/ReturnSet.kt"); } + @TestMetadata("SealedForAlreadySealed.kt") + public void testSealedForAlreadySealed() throws Exception { + runTest("idea/idea-completion/testData/keywords/SealedForAlreadySealed.kt"); + } + + @TestMetadata("SealedForAnnotationClass.kt") + public void testSealedForAnnotationClass() throws Exception { + runTest("idea/idea-completion/testData/keywords/SealedForAnnotationClass.kt"); + } + + @TestMetadata("SealedForDataClass.kt") + public void testSealedForDataClass() throws Exception { + runTest("idea/idea-completion/testData/keywords/SealedForDataClass.kt"); + } + @TestMetadata("SealedForDeclaredClass.kt") public void testSealedForDeclaredClass() throws Exception { runTest("idea/idea-completion/testData/keywords/SealedForDeclaredClass.kt"); @@ -513,6 +528,26 @@ public class KeywordCompletionTestGenerated extends AbstractKeywordCompletionTes runTest("idea/idea-completion/testData/keywords/SealedForDeclaredInterface.kt"); } + @TestMetadata("SealedForEnumClass.kt") + public void testSealedForEnumClass() throws Exception { + runTest("idea/idea-completion/testData/keywords/SealedForEnumClass.kt"); + } + + @TestMetadata("SealedForFunInterface.kt") + public void testSealedForFunInterface() throws Exception { + runTest("idea/idea-completion/testData/keywords/SealedForFunInterface.kt"); + } + + @TestMetadata("SealedForInnerClass.kt") + public void testSealedForInnerClass() throws Exception { + runTest("idea/idea-completion/testData/keywords/SealedForInnerClass.kt"); + } + + @TestMetadata("SealedForOpenClass.kt") + public void testSealedForOpenClass() throws Exception { + runTest("idea/idea-completion/testData/keywords/SealedForOpenClass.kt"); + } + @TestMetadata("SealedWithName.kt") public void testSealedWithName() throws Exception { runTest("idea/idea-completion/testData/keywords/SealedWithName.kt");