Keyword completion: add "class/interface/object + filename" completion for top level (KT-28394)

#KT-28394 Fixed
This commit is contained in:
Toshiaki Kameyama
2018-12-05 11:23:01 +03:00
committed by Nikolay Krasko
parent ed9d53acaa
commit 827e04ae3e
12 changed files with 160 additions and 18 deletions
@@ -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<LookupElement> {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
WithTailInsertHandler(":", spaceBefore = false, spaceAfter = false).postHandleInsert(context, item)
@@ -10,6 +10,9 @@ class B {
}
}
class AfterClasses {
}
<caret>
// EXIST: abstract
@@ -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
@@ -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
@@ -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
@@ -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
+7
View File
@@ -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
+32
View File
@@ -0,0 +1,32 @@
<caret>
// 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
+32
View File
@@ -0,0 +1,32 @@
<caret>
// 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
@@ -0,0 +1,4 @@
cla<caret>
// ORDER: class
// ORDER: class TopLevelKeyword
@@ -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");
@@ -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");