Keyword completion: add "class/interface/object + filename" completion for top level (KT-28394)
#KT-28394 Fixed
This commit is contained in:
committed by
Nikolay Krasko
parent
ed9d53acaa
commit
827e04ae3e
+39
-18
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
+10
@@ -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");
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user