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.LookupElement
|
||||||
import com.intellij.codeInsight.lookup.LookupElementBuilder
|
import com.intellij.codeInsight.lookup.LookupElementBuilder
|
||||||
import com.intellij.openapi.module.ModuleUtilCore
|
import com.intellij.openapi.module.ModuleUtilCore
|
||||||
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
import com.intellij.psi.*
|
import com.intellij.psi.*
|
||||||
import com.intellij.psi.filters.*
|
import com.intellij.psi.filters.*
|
||||||
import com.intellij.psi.filters.position.LeftNeighbour
|
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.KtModifierKeywordToken
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
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.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||||
|
import org.jetbrains.kotlin.renderer.render
|
||||||
import org.jetbrains.kotlin.resolve.ModifierCheckerCore
|
import org.jetbrains.kotlin.resolve.ModifierCheckerCore
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
|
|
||||||
@@ -124,29 +127,47 @@ object KeywordCompletion {
|
|||||||
consumer(element)
|
consumer(element)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var element = LookupElementBuilder.create(KeywordLookupObject(), keyword).bold()
|
if (listOf(CLASS_KEYWORD, OBJECT_KEYWORD, INTERFACE_KEYWORD).any { keyword.endsWith(it.value) }) {
|
||||||
|
val topLevelClassName = getTopLevelClassName(position)
|
||||||
val isUseSiteAnnotationTarget = position.prevLeaf()?.node?.elementType == KtTokens.AT
|
if (topLevelClassName != null) {
|
||||||
|
consumer(createLookupElementBuilder("$keyword $topLevelClassName", position))
|
||||||
val insertHandler = when {
|
}
|
||||||
isUseSiteAnnotationTarget -> UseSiteAnnotationTargetInsertHandler
|
|
||||||
|
|
||||||
keyword in NO_SPACE_AFTER -> null
|
|
||||||
|
|
||||||
else -> SpaceAfterInsertHandler
|
|
||||||
}
|
}
|
||||||
|
consumer(createLookupElementBuilder(keyword, position))
|
||||||
element = element.withInsertHandler(insertHandler)
|
|
||||||
|
|
||||||
if (isUseSiteAnnotationTarget) {
|
|
||||||
element = element.withPresentableText(keyword + ":")
|
|
||||||
}
|
|
||||||
|
|
||||||
consumer(element)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> {
|
private object UseSiteAnnotationTargetInsertHandler : InsertHandler<LookupElement> {
|
||||||
override fun handleInsert(context: InsertionContext, item: LookupElement) {
|
override fun handleInsert(context: InsertionContext, item: LookupElement) {
|
||||||
WithTailInsertHandler(":", spaceBefore = false, spaceAfter = false).postHandleInsert(context, item)
|
WithTailInsertHandler(":", spaceBefore = false, spaceAfter = false).postHandleInsert(context, item)
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ class B {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AfterClasses {
|
||||||
|
}
|
||||||
|
|
||||||
<caret>
|
<caret>
|
||||||
|
|
||||||
// EXIST: abstract
|
// EXIST: abstract
|
||||||
|
|||||||
@@ -14,24 +14,31 @@ class B {
|
|||||||
|
|
||||||
// EXIST: abstract
|
// EXIST: abstract
|
||||||
// EXIST: class
|
// EXIST: class
|
||||||
|
// EXIST: class AfterClasses_LangLevel10
|
||||||
// EXIST: enum class
|
// EXIST: enum class
|
||||||
|
// EXIST: enum class AfterClasses_LangLevel10
|
||||||
// EXIST: final
|
// EXIST: final
|
||||||
// EXIST: fun
|
// EXIST: fun
|
||||||
// EXIST: internal
|
// EXIST: internal
|
||||||
// EXIST: object
|
// EXIST: object
|
||||||
|
// EXIST: object AfterClasses_LangLevel10
|
||||||
// EXIST: open
|
// EXIST: open
|
||||||
// EXIST: private
|
// EXIST: private
|
||||||
// EXIST: public
|
// EXIST: public
|
||||||
// EXIST: interface
|
// EXIST: interface
|
||||||
|
// EXIST: interface AfterClasses_LangLevel10
|
||||||
// EXIST: val
|
// EXIST: val
|
||||||
// EXIST: var
|
// EXIST: var
|
||||||
// EXIST: operator
|
// EXIST: operator
|
||||||
// EXIST: infix
|
// EXIST: infix
|
||||||
// EXIST: sealed class
|
// EXIST: sealed class
|
||||||
|
// EXIST: sealed class AfterClasses_LangLevel10
|
||||||
// EXIST: data class
|
// EXIST: data class
|
||||||
|
// EXIST: data class AfterClasses_LangLevel10
|
||||||
// EXIST: inline
|
// EXIST: inline
|
||||||
// EXIST: tailrec
|
// EXIST: tailrec
|
||||||
// EXIST: external
|
// EXIST: external
|
||||||
// EXIST: annotation class
|
// EXIST: annotation class
|
||||||
|
// EXIST: annotation class AfterClasses_LangLevel10
|
||||||
// EXIST: const val
|
// EXIST: const val
|
||||||
// NOTHING_ELSE
|
// NOTHING_ELSE
|
||||||
|
|||||||
@@ -14,25 +14,32 @@ class B {
|
|||||||
|
|
||||||
// EXIST: abstract
|
// EXIST: abstract
|
||||||
// EXIST: class
|
// EXIST: class
|
||||||
|
// EXIST: class AfterClasses_LangLevel11
|
||||||
// EXIST: enum class
|
// EXIST: enum class
|
||||||
|
// EXIST: enum class AfterClasses_LangLevel11
|
||||||
// EXIST: final
|
// EXIST: final
|
||||||
// EXIST: fun
|
// EXIST: fun
|
||||||
// EXIST: internal
|
// EXIST: internal
|
||||||
// EXIST: object
|
// EXIST: object
|
||||||
|
// EXIST: object AfterClasses_LangLevel11
|
||||||
// EXIST: open
|
// EXIST: open
|
||||||
// EXIST: private
|
// EXIST: private
|
||||||
// EXIST: public
|
// EXIST: public
|
||||||
// EXIST: interface
|
// EXIST: interface
|
||||||
|
// EXIST: interface AfterClasses_LangLevel11
|
||||||
// EXIST: val
|
// EXIST: val
|
||||||
// EXIST: var
|
// EXIST: var
|
||||||
// EXIST: operator
|
// EXIST: operator
|
||||||
// EXIST: infix
|
// EXIST: infix
|
||||||
// EXIST: sealed class
|
// EXIST: sealed class
|
||||||
|
// EXIST: sealed class AfterClasses_LangLevel11
|
||||||
// EXIST: data class
|
// EXIST: data class
|
||||||
|
// EXIST: data class AfterClasses_LangLevel11
|
||||||
// EXIST: inline
|
// EXIST: inline
|
||||||
// EXIST: tailrec
|
// EXIST: tailrec
|
||||||
// EXIST: external
|
// EXIST: external
|
||||||
// EXIST: annotation class
|
// EXIST: annotation class
|
||||||
|
// EXIST: annotation class AfterClasses_LangLevel11
|
||||||
// EXIST: const val
|
// EXIST: const val
|
||||||
// EXIST: suspend
|
// EXIST: suspend
|
||||||
// EXIST: typealias
|
// EXIST: typealias
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ var a : Int
|
|||||||
// EXIST: abstract
|
// EXIST: abstract
|
||||||
// EXIST: by
|
// EXIST: by
|
||||||
// EXIST: class
|
// EXIST: class
|
||||||
|
// EXIST: class GlobalPropertyAccessors
|
||||||
// EXIST: enum class
|
// EXIST: enum class
|
||||||
|
// EXIST: enum class GlobalPropertyAccessors
|
||||||
// EXIST: final
|
// EXIST: final
|
||||||
// EXIST: fun
|
// EXIST: fun
|
||||||
// EXIST: { itemText: "get", tailText: null }
|
// EXIST: { itemText: "get", tailText: null }
|
||||||
@@ -20,6 +22,7 @@ var a : Int
|
|||||||
// EXIST: { itemText: "get", tailText: "() {...}" }
|
// EXIST: { itemText: "get", tailText: "() {...}" }
|
||||||
// EXIST: internal
|
// EXIST: internal
|
||||||
// EXIST: object
|
// EXIST: object
|
||||||
|
// EXIST: object GlobalPropertyAccessors
|
||||||
// EXIST: open
|
// EXIST: open
|
||||||
// EXIST: private
|
// EXIST: private
|
||||||
// EXIST: public
|
// EXIST: public
|
||||||
@@ -27,16 +30,20 @@ var a : Int
|
|||||||
// EXIST: { itemText: "set", tailText: "(value) = ..." }
|
// EXIST: { itemText: "set", tailText: "(value) = ..." }
|
||||||
// EXIST: { itemText: "set", tailText: "(value) {...}" }
|
// EXIST: { itemText: "set", tailText: "(value) {...}" }
|
||||||
// EXIST: interface
|
// EXIST: interface
|
||||||
|
// EXIST: interface GlobalPropertyAccessors
|
||||||
// EXIST: val
|
// EXIST: val
|
||||||
// EXIST: var
|
// EXIST: var
|
||||||
// EXIST: operator
|
// EXIST: operator
|
||||||
// EXIST: infix
|
// EXIST: infix
|
||||||
// EXIST: sealed class
|
// EXIST: sealed class
|
||||||
|
// EXIST: sealed class GlobalPropertyAccessors
|
||||||
// EXIST: data class
|
// EXIST: data class
|
||||||
|
// EXIST: data class GlobalPropertyAccessors
|
||||||
// EXIST: inline
|
// EXIST: inline
|
||||||
// EXIST: tailrec
|
// EXIST: tailrec
|
||||||
// EXIST: external
|
// EXIST: external
|
||||||
// EXIST: annotation class
|
// EXIST: annotation class
|
||||||
|
// EXIST: annotation class GlobalPropertyAccessors
|
||||||
// EXIST: const val
|
// EXIST: const val
|
||||||
// EXIST: suspend
|
// EXIST: suspend
|
||||||
// EXIST: typealias
|
// EXIST: typealias
|
||||||
|
|||||||
@@ -4,26 +4,33 @@ package Test
|
|||||||
|
|
||||||
// EXIST: abstract
|
// EXIST: abstract
|
||||||
// EXIST: class
|
// EXIST: class
|
||||||
|
// EXIST: class InTopScopeAfterPackage
|
||||||
// EXIST: enum class
|
// EXIST: enum class
|
||||||
|
// EXIST: enum class InTopScopeAfterPackage
|
||||||
// EXIST: final
|
// EXIST: final
|
||||||
// EXIST: fun
|
// EXIST: fun
|
||||||
// EXIST: import
|
// EXIST: import
|
||||||
// EXIST: internal
|
// EXIST: internal
|
||||||
// EXIST: object
|
// EXIST: object
|
||||||
|
// EXIST: object InTopScopeAfterPackage
|
||||||
// EXIST: open
|
// EXIST: open
|
||||||
// EXIST: private
|
// EXIST: private
|
||||||
// EXIST: public
|
// EXIST: public
|
||||||
// EXIST: interface
|
// EXIST: interface
|
||||||
|
// EXIST: interface InTopScopeAfterPackage
|
||||||
// EXIST: val
|
// EXIST: val
|
||||||
// EXIST: var
|
// EXIST: var
|
||||||
// EXIST: operator
|
// EXIST: operator
|
||||||
// EXIST: infix
|
// EXIST: infix
|
||||||
// EXIST: sealed class
|
// EXIST: sealed class
|
||||||
|
// EXIST: sealed class InTopScopeAfterPackage
|
||||||
// EXIST: data class
|
// EXIST: data class
|
||||||
|
// EXIST: data class InTopScopeAfterPackage
|
||||||
// EXIST: inline
|
// EXIST: inline
|
||||||
// EXIST: tailrec
|
// EXIST: tailrec
|
||||||
// EXIST: external
|
// EXIST: external
|
||||||
// EXIST: annotation class
|
// EXIST: annotation class
|
||||||
|
// EXIST: annotation class InTopScopeAfterPackage
|
||||||
// EXIST: const val
|
// EXIST: const val
|
||||||
// EXIST: suspend
|
// EXIST: suspend
|
||||||
// EXIST: typealias
|
// EXIST: typealias
|
||||||
|
|||||||
@@ -2,27 +2,34 @@
|
|||||||
|
|
||||||
// EXIST: abstract
|
// EXIST: abstract
|
||||||
// EXIST: class
|
// EXIST: class
|
||||||
|
// EXIST: class TopScope
|
||||||
// EXIST: enum class
|
// EXIST: enum class
|
||||||
|
// EXIST: enum class TopScope
|
||||||
// EXIST: final
|
// EXIST: final
|
||||||
// EXIST: fun
|
// EXIST: fun
|
||||||
// EXIST: import
|
// EXIST: import
|
||||||
// EXIST: internal
|
// EXIST: internal
|
||||||
// EXIST: object
|
// EXIST: object
|
||||||
|
// EXIST: object TopScope
|
||||||
// EXIST: open
|
// EXIST: open
|
||||||
// EXIST: package
|
// EXIST: package
|
||||||
// EXIST: private
|
// EXIST: private
|
||||||
// EXIST: public
|
// EXIST: public
|
||||||
// EXIST: interface
|
// EXIST: interface
|
||||||
|
// EXIST: interface TopScope
|
||||||
// EXIST: val
|
// EXIST: val
|
||||||
// EXIST: var
|
// EXIST: var
|
||||||
// EXIST: operator
|
// EXIST: operator
|
||||||
// EXIST: infix
|
// EXIST: infix
|
||||||
// EXIST: sealed class
|
// EXIST: sealed class
|
||||||
|
// EXIST: sealed class TopScope
|
||||||
// EXIST: data class
|
// EXIST: data class
|
||||||
|
// EXIST: data class TopScope
|
||||||
// EXIST: inline
|
// EXIST: inline
|
||||||
// EXIST: tailrec
|
// EXIST: tailrec
|
||||||
// EXIST: external
|
// EXIST: external
|
||||||
// EXIST: annotation class
|
// EXIST: annotation class
|
||||||
|
// EXIST: annotation class TopScope
|
||||||
// EXIST: const val
|
// EXIST: const val
|
||||||
// EXIST: suspend
|
// EXIST: suspend
|
||||||
// EXIST: typealias
|
// 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");
|
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")
|
@TestMetadata("UseSiteTargetForPrimaryConstructorParameter.kt")
|
||||||
public void testUseSiteTargetForPrimaryConstructorParameter() throws Exception {
|
public void testUseSiteTargetForPrimaryConstructorParameter() throws Exception {
|
||||||
runTest("idea/idea-completion/testData/keywords/UseSiteTargetForPrimaryConstructorParameter.kt");
|
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");
|
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")
|
@TestMetadata("UnavailableDslReceiver.kt")
|
||||||
public void testUnavailableDslReceiver() throws Exception {
|
public void testUnavailableDslReceiver() throws Exception {
|
||||||
runTest("idea/idea-completion/testData/weighers/basic/UnavailableDslReceiver.kt");
|
runTest("idea/idea-completion/testData/weighers/basic/UnavailableDslReceiver.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user