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 0c82333a23b..26a992b166c 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 @@ -21,6 +21,7 @@ import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.openapi.module.ModuleUtilCore +import com.intellij.openapi.util.io.FileUtil import com.intellij.psi.* import com.intellij.psi.filters.* import com.intellij.psi.filters.position.LeftNeighbour @@ -40,8 +41,10 @@ import org.jetbrains.kotlin.lexer.KtKeywordToken import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens.* +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.renderer.render import org.jetbrains.kotlin.resolve.ModifierCheckerCore import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull @@ -124,29 +127,47 @@ object KeywordCompletion { consumer(element) } else { - var element = LookupElementBuilder.create(KeywordLookupObject(), keyword).bold() - - val isUseSiteAnnotationTarget = position.prevLeaf()?.node?.elementType == KtTokens.AT - - val insertHandler = when { - isUseSiteAnnotationTarget -> UseSiteAnnotationTargetInsertHandler - - keyword in NO_SPACE_AFTER -> null - - else -> SpaceAfterInsertHandler + if (listOf(CLASS_KEYWORD, OBJECT_KEYWORD, INTERFACE_KEYWORD).any { keyword.endsWith(it.value) }) { + val topLevelClassName = getTopLevelClassName(position) + if (topLevelClassName != null) { + consumer(createLookupElementBuilder("$keyword $topLevelClassName", position)) + } } - - element = element.withInsertHandler(insertHandler) - - if (isUseSiteAnnotationTarget) { - element = element.withPresentableText(keyword + ":") - } - - consumer(element) + consumer(createLookupElementBuilder(keyword, position)) } } } + private fun createLookupElementBuilder( + keyword: String, + position: PsiElement + ): LookupElementBuilder { + val isUseSiteAnnotationTarget = position.prevLeaf()?.node?.elementType == AT + val insertHandler = when { + isUseSiteAnnotationTarget -> UseSiteAnnotationTargetInsertHandler + keyword in NO_SPACE_AFTER -> null + else -> SpaceAfterInsertHandler + } + val element = LookupElementBuilder.create(KeywordLookupObject(), keyword).bold().withInsertHandler(insertHandler) + return if (isUseSiteAnnotationTarget) { + element.withPresentableText("$keyword:") + } else { + element + } + } + + private fun getTopLevelClassName(position: PsiElement): String? { + if (position.parents.any { it is KtDeclaration }) return null + val file = position.containingFile as? KtFile ?: return null + val name = FileUtil.getNameWithoutExtension(file.name) + if (!Name.isValidIdentifier(name) + || Name.identifier(name).render() != name + || !name[0].isUpperCase() + || file.declarations.any { it is KtClassOrObject && it.name == name } + ) return null + return name + } + private object UseSiteAnnotationTargetInsertHandler : InsertHandler { override fun handleInsert(context: InsertionContext, item: LookupElement) { WithTailInsertHandler(":", spaceBefore = false, spaceAfter = false).postHandleInsert(context, item) diff --git a/idea/idea-completion/testData/keywords/AfterClasses.kt b/idea/idea-completion/testData/keywords/AfterClasses.kt index 527c8b419f6..38903eee906 100644 --- a/idea/idea-completion/testData/keywords/AfterClasses.kt +++ b/idea/idea-completion/testData/keywords/AfterClasses.kt @@ -10,6 +10,9 @@ class B { } } +class AfterClasses { +} + // EXIST: abstract diff --git a/idea/idea-completion/testData/keywords/AfterClasses_LangLevel10.kt b/idea/idea-completion/testData/keywords/AfterClasses_LangLevel10.kt index 61f2b2e0c6f..4e06e21833b 100644 --- a/idea/idea-completion/testData/keywords/AfterClasses_LangLevel10.kt +++ b/idea/idea-completion/testData/keywords/AfterClasses_LangLevel10.kt @@ -14,24 +14,31 @@ class B { // EXIST: abstract // EXIST: class +// EXIST: class AfterClasses_LangLevel10 // EXIST: enum class +// EXIST: enum class AfterClasses_LangLevel10 // EXIST: final // EXIST: fun // EXIST: internal // EXIST: object +// EXIST: object AfterClasses_LangLevel10 // EXIST: open // EXIST: private // EXIST: public // EXIST: interface +// EXIST: interface AfterClasses_LangLevel10 // EXIST: val // EXIST: var // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed class AfterClasses_LangLevel10 // EXIST: data class +// EXIST: data class AfterClasses_LangLevel10 // EXIST: inline // EXIST: tailrec // EXIST: external // EXIST: annotation class +// EXIST: annotation class AfterClasses_LangLevel10 // EXIST: const val // NOTHING_ELSE diff --git a/idea/idea-completion/testData/keywords/AfterClasses_LangLevel11.kt b/idea/idea-completion/testData/keywords/AfterClasses_LangLevel11.kt index 7d26d17cffc..86576902a31 100644 --- a/idea/idea-completion/testData/keywords/AfterClasses_LangLevel11.kt +++ b/idea/idea-completion/testData/keywords/AfterClasses_LangLevel11.kt @@ -14,25 +14,32 @@ class B { // EXIST: abstract // EXIST: class +// EXIST: class AfterClasses_LangLevel11 // EXIST: enum class +// EXIST: enum class AfterClasses_LangLevel11 // EXIST: final // EXIST: fun // EXIST: internal // EXIST: object +// EXIST: object AfterClasses_LangLevel11 // EXIST: open // EXIST: private // EXIST: public // EXIST: interface +// EXIST: interface AfterClasses_LangLevel11 // EXIST: val // EXIST: var // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed class AfterClasses_LangLevel11 // EXIST: data class +// EXIST: data class AfterClasses_LangLevel11 // EXIST: inline // EXIST: tailrec // EXIST: external // EXIST: annotation class +// EXIST: annotation class AfterClasses_LangLevel11 // EXIST: const val // EXIST: suspend // EXIST: typealias diff --git a/idea/idea-completion/testData/keywords/GlobalPropertyAccessors.kt b/idea/idea-completion/testData/keywords/GlobalPropertyAccessors.kt index 4d50db89ac2..6da4ab0c5c3 100644 --- a/idea/idea-completion/testData/keywords/GlobalPropertyAccessors.kt +++ b/idea/idea-completion/testData/keywords/GlobalPropertyAccessors.kt @@ -12,7 +12,9 @@ var a : Int // EXIST: abstract // EXIST: by // EXIST: class +// EXIST: class GlobalPropertyAccessors // EXIST: enum class +// EXIST: enum class GlobalPropertyAccessors // EXIST: final // EXIST: fun // EXIST: { itemText: "get", tailText: null } @@ -20,6 +22,7 @@ var a : Int // EXIST: { itemText: "get", tailText: "() {...}" } // EXIST: internal // EXIST: object +// EXIST: object GlobalPropertyAccessors // EXIST: open // EXIST: private // EXIST: public @@ -27,16 +30,20 @@ var a : Int // EXIST: { itemText: "set", tailText: "(value) = ..." } // EXIST: { itemText: "set", tailText: "(value) {...}" } // EXIST: interface +// EXIST: interface GlobalPropertyAccessors // EXIST: val // EXIST: var // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed class GlobalPropertyAccessors // EXIST: data class +// EXIST: data class GlobalPropertyAccessors // EXIST: inline // EXIST: tailrec // EXIST: external // EXIST: annotation class +// EXIST: annotation class GlobalPropertyAccessors // EXIST: const val // EXIST: suspend // EXIST: typealias diff --git a/idea/idea-completion/testData/keywords/InTopScopeAfterPackage.kt b/idea/idea-completion/testData/keywords/InTopScopeAfterPackage.kt index a0b8094f91a..b8c72b8abff 100644 --- a/idea/idea-completion/testData/keywords/InTopScopeAfterPackage.kt +++ b/idea/idea-completion/testData/keywords/InTopScopeAfterPackage.kt @@ -4,26 +4,33 @@ package Test // EXIST: abstract // EXIST: class +// EXIST: class InTopScopeAfterPackage // EXIST: enum class +// EXIST: enum class InTopScopeAfterPackage // EXIST: final // EXIST: fun // EXIST: import // EXIST: internal // EXIST: object +// EXIST: object InTopScopeAfterPackage // EXIST: open // EXIST: private // EXIST: public // EXIST: interface +// EXIST: interface InTopScopeAfterPackage // EXIST: val // EXIST: var // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed class InTopScopeAfterPackage // EXIST: data class +// EXIST: data class InTopScopeAfterPackage // EXIST: inline // EXIST: tailrec // EXIST: external // EXIST: annotation class +// EXIST: annotation class InTopScopeAfterPackage // EXIST: const val // EXIST: suspend // EXIST: typealias diff --git a/idea/idea-completion/testData/keywords/TopScope.kt b/idea/idea-completion/testData/keywords/TopScope.kt index f8fa298a9a0..457ba1ecea3 100644 --- a/idea/idea-completion/testData/keywords/TopScope.kt +++ b/idea/idea-completion/testData/keywords/TopScope.kt @@ -2,27 +2,34 @@ // EXIST: abstract // EXIST: class +// EXIST: class TopScope // EXIST: enum class +// EXIST: enum class TopScope // EXIST: final // EXIST: fun // EXIST: import // EXIST: internal // EXIST: object +// EXIST: object TopScope // EXIST: open // EXIST: package // EXIST: private // EXIST: public // EXIST: interface +// EXIST: interface TopScope // EXIST: val // EXIST: var // EXIST: operator // EXIST: infix // EXIST: sealed class +// EXIST: sealed class TopScope // EXIST: data class +// EXIST: data class TopScope // EXIST: inline // EXIST: tailrec // EXIST: external // EXIST: annotation class +// EXIST: annotation class TopScope // EXIST: const val // EXIST: suspend // EXIST: typealias diff --git a/idea/idea-completion/testData/keywords/TopScope3-.kt b/idea/idea-completion/testData/keywords/TopScope3-.kt new file mode 100644 index 00000000000..f8fa298a9a0 --- /dev/null +++ b/idea/idea-completion/testData/keywords/TopScope3-.kt @@ -0,0 +1,32 @@ + + +// EXIST: abstract +// EXIST: class +// EXIST: enum class +// EXIST: final +// EXIST: fun +// EXIST: import +// EXIST: internal +// EXIST: object +// EXIST: open +// EXIST: package +// EXIST: private +// EXIST: public +// EXIST: interface +// EXIST: val +// EXIST: var +// EXIST: operator +// EXIST: infix +// EXIST: sealed class +// EXIST: data class +// EXIST: inline +// EXIST: tailrec +// EXIST: external +// EXIST: annotation class +// EXIST: const val +// EXIST: suspend +// EXIST: typealias +// EXIST: expect +// EXIST: actual +// EXIST: lateinit var +// NOTHING_ELSE diff --git a/idea/idea-completion/testData/keywords/topScope2.kt b/idea/idea-completion/testData/keywords/topScope2.kt new file mode 100644 index 00000000000..f8fa298a9a0 --- /dev/null +++ b/idea/idea-completion/testData/keywords/topScope2.kt @@ -0,0 +1,32 @@ + + +// EXIST: abstract +// EXIST: class +// EXIST: enum class +// EXIST: final +// EXIST: fun +// EXIST: import +// EXIST: internal +// EXIST: object +// EXIST: open +// EXIST: package +// EXIST: private +// EXIST: public +// EXIST: interface +// EXIST: val +// EXIST: var +// EXIST: operator +// EXIST: infix +// EXIST: sealed class +// EXIST: data class +// EXIST: inline +// EXIST: tailrec +// EXIST: external +// EXIST: annotation class +// EXIST: const val +// EXIST: suspend +// EXIST: typealias +// EXIST: expect +// EXIST: actual +// EXIST: lateinit var +// NOTHING_ELSE diff --git a/idea/idea-completion/testData/weighers/basic/TopLevelKeywordWithClassName.kt b/idea/idea-completion/testData/weighers/basic/TopLevelKeywordWithClassName.kt new file mode 100644 index 00000000000..8dde6827cd3 --- /dev/null +++ b/idea/idea-completion/testData/weighers/basic/TopLevelKeywordWithClassName.kt @@ -0,0 +1,4 @@ +cla + +// ORDER: class +// ORDER: class TopLevelKeyword 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 03ffea7f349..948b2d18bd6 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 @@ -519,6 +519,16 @@ public class KeywordCompletionTestGenerated extends AbstractKeywordCompletionTes runTest("idea/idea-completion/testData/keywords/TopScope.kt"); } + @TestMetadata("topScope2.kt") + public void testTopScope2() throws Exception { + runTest("idea/idea-completion/testData/keywords/topScope2.kt"); + } + + @TestMetadata("TopScope3-.kt") + public void testTopScope3_() throws Exception { + runTest("idea/idea-completion/testData/keywords/TopScope3-.kt"); + } + @TestMetadata("UseSiteTargetForPrimaryConstructorParameter.kt") public void testUseSiteTargetForPrimaryConstructorParameter() throws Exception { runTest("idea/idea-completion/testData/keywords/UseSiteTargetForPrimaryConstructorParameter.kt"); diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/weighers/BasicCompletionWeigherTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/weighers/BasicCompletionWeigherTestGenerated.java index 858b7a79728..98def0f5913 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/weighers/BasicCompletionWeigherTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/weighers/BasicCompletionWeigherTestGenerated.java @@ -204,6 +204,11 @@ public class BasicCompletionWeigherTestGenerated extends AbstractBasicCompletion runTest("idea/idea-completion/testData/weighers/basic/SuperMembers.kt"); } + @TestMetadata("TopLevelKeywordWithClassName.kt") + public void testTopLevelKeywordWithClassName() throws Exception { + runTest("idea/idea-completion/testData/weighers/basic/TopLevelKeywordWithClassName.kt"); + } + @TestMetadata("UnavailableDslReceiver.kt") public void testUnavailableDslReceiver() throws Exception { runTest("idea/idea-completion/testData/weighers/basic/UnavailableDslReceiver.kt");