KT-13220: Add completion for variable names
Using parameter name completion #KT-13220 fixed
This commit is contained in:
+29
-2
@@ -195,6 +195,10 @@ class BasicCompletionSession(
|
||||
if (declaration != null) {
|
||||
completeDeclarationNameFromUnresolvedOrOverride(declaration)
|
||||
|
||||
if (declaration is KtProperty) {
|
||||
completeVariableName(declaration.modifierList?.hasModifier(KtTokens.LATEINIT_KEYWORD) == true)
|
||||
}
|
||||
|
||||
// no auto-popup on typing after "val", "var" and "fun" because it's likely the name of the declaration which is being typed by user
|
||||
if (parameters.invocationCount == 0) {
|
||||
val suppressOtherCompletion = when (declaration) {
|
||||
@@ -475,6 +479,29 @@ class BasicCompletionSession(
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun completeVariableName(withType: Boolean) {
|
||||
val variableNameAndTypeCompletion = VariableOrParameterNameWithTypeCompletion(collector, basicLookupElementFactory, prefixMatcher, resolutionFacade, withType)
|
||||
|
||||
// if we are typing parameter name, restart completion each time we type an upper case letter because new suggestions will appear (previous words can be used as user prefix)
|
||||
val prefixPattern = StandardPatterns.string().with(object : PatternCondition<String>("Prefix ends with uppercase letter") {
|
||||
override fun accepts(prefix: String, context: ProcessingContext?) = prefix.isNotEmpty() && prefix.last().isUpperCase()
|
||||
})
|
||||
collector.restartCompletionOnPrefixChange(prefixPattern)
|
||||
|
||||
collector.addLookupElementPostProcessor { lookupElement ->
|
||||
lookupElement.putUserData(KotlinCompletionCharFilter.HIDE_LOOKUP_ON_COLON, Unit)
|
||||
lookupElement
|
||||
}
|
||||
|
||||
variableNameAndTypeCompletion.addFromParametersInFile(position, resolutionFacade, isVisibleFilterCheckAlways)
|
||||
flushToResultSet()
|
||||
|
||||
variableNameAndTypeCompletion.addFromImportedClasses(position, bindingContext, isVisibleFilterCheckAlways)
|
||||
flushToResultSet()
|
||||
|
||||
variableNameAndTypeCompletion.addFromAllClasses(parameters, indicesHelper(false))
|
||||
}
|
||||
}
|
||||
|
||||
private val KEYWORDS_ONLY = object : CompletionKind {
|
||||
@@ -641,13 +668,13 @@ class BasicCompletionSession(
|
||||
|
||||
override fun addWeighers(sorter: CompletionSorter): CompletionSorter {
|
||||
if (shouldCompleteParameterNameAndType()) {
|
||||
return sorter.weighBefore("prefix", ParameterNameAndTypeCompletion.Weigher)
|
||||
return sorter.weighBefore("prefix", VariableOrParameterNameWithTypeCompletion.Weigher)
|
||||
}
|
||||
return sorter
|
||||
}
|
||||
|
||||
private fun completeParameterNameAndType() {
|
||||
val parameterNameAndTypeCompletion = ParameterNameAndTypeCompletion(collector, basicLookupElementFactory, prefixMatcher, resolutionFacade)
|
||||
val parameterNameAndTypeCompletion = VariableOrParameterNameWithTypeCompletion(collector, basicLookupElementFactory, prefixMatcher, resolutionFacade, true)
|
||||
|
||||
// if we are typing parameter name, restart completion each time we type an upper case letter because new suggestions will appear (previous words can be used as user prefix)
|
||||
val prefixPattern = StandardPatterns.string().with(object : PatternCondition<String>("Prefix ends with uppercase letter") {
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ class KotlinCompletionContributor : CompletionContributor() {
|
||||
if (tokenAt.node.elementType == KtTokens.IDENTIFIER) {
|
||||
val parameter = tokenAt.parent as? KtParameter
|
||||
if (parameter != null) {
|
||||
context.offsetMap.addOffset(ParameterNameAndTypeCompletion.REPLACEMENT_OFFSET, parameter.endOffset)
|
||||
context.offsetMap.addOffset(VariableOrParameterNameWithTypeCompletion.REPLACEMENT_OFFSET, parameter.endOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
-14
@@ -47,11 +47,12 @@ import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.isError
|
||||
import java.util.*
|
||||
|
||||
class ParameterNameAndTypeCompletion(
|
||||
class VariableOrParameterNameWithTypeCompletion(
|
||||
private val collector: LookupElementsCollector,
|
||||
private val lookupElementFactory: BasicLookupElementFactory,
|
||||
private val prefixMatcher: PrefixMatcher,
|
||||
private val resolutionFacade: ResolutionFacade
|
||||
private val resolutionFacade: ResolutionFacade,
|
||||
private val withType: Boolean
|
||||
) {
|
||||
private val userPrefixes: List<String>
|
||||
private val classNamePrefixMatchers: List<PrefixMatcher>
|
||||
@@ -114,7 +115,7 @@ class ParameterNameAndTypeCompletion(
|
||||
if (descriptor != null) {
|
||||
val parameterType = descriptor.type
|
||||
if (parameterType.isVisible(visibilityFilter)) {
|
||||
val lookupElement = MyLookupElement.create(name, ArbitraryType(parameterType), lookupElementFactory)!!
|
||||
val lookupElement = MyLookupElement.create(name, ArbitraryType(parameterType), withType, lookupElementFactory)!!
|
||||
val count = lookupElementToCount[lookupElement] ?: 0
|
||||
lookupElementToCount[lookupElement] = count + 1
|
||||
}
|
||||
@@ -144,7 +145,7 @@ class ParameterNameAndTypeCompletion(
|
||||
for (name in nameSuggestions) {
|
||||
val parameterName = userPrefix + name
|
||||
if (prefixMatcher.isStartMatch(parameterName)) {
|
||||
val lookupElement = MyLookupElement.create(parameterName, type, lookupElementFactory)
|
||||
val lookupElement = MyLookupElement.create(parameterName, type, withType, lookupElementFactory)
|
||||
if (lookupElement != null) {
|
||||
lookupElement.putUserData(PRIORITY_KEY, userPrefix.length) // suggestions with longer user prefix get lower priority
|
||||
collector.addElement(lookupElement, notImported)
|
||||
@@ -185,13 +186,14 @@ class ParameterNameAndTypeCompletion(
|
||||
private class MyLookupElement private constructor(
|
||||
private val parameterName: String,
|
||||
private val type: Type,
|
||||
typeLookupElement: LookupElement
|
||||
typeLookupElement: LookupElement,
|
||||
private val shouldInsertType: Boolean
|
||||
) : LookupElementDecorator<LookupElement>(typeLookupElement) {
|
||||
|
||||
companion object {
|
||||
fun create(parameterName: String, type: Type, factory: BasicLookupElementFactory): LookupElement? {
|
||||
fun create(parameterName: String, type: Type, shouldInsertType: Boolean, factory: BasicLookupElementFactory): LookupElement? {
|
||||
val typeLookupElement = type.createTypeLookupElement(factory) ?: return null
|
||||
val lookupElement = MyLookupElement(parameterName, type, typeLookupElement)
|
||||
val lookupElement = MyLookupElement(parameterName, type, typeLookupElement, shouldInsertType)
|
||||
return lookupElement.suppressAutoInsertion()
|
||||
}
|
||||
}
|
||||
@@ -204,7 +206,13 @@ class ParameterNameAndTypeCompletion(
|
||||
|
||||
override fun renderElement(presentation: LookupElementPresentation) {
|
||||
super.renderElement(presentation)
|
||||
presentation.itemText = parameterName + ": " + presentation.itemText
|
||||
if (shouldInsertType) {
|
||||
presentation.itemText = parameterName + ": " + presentation.itemText
|
||||
}
|
||||
else {
|
||||
presentation.prependTailText(": " + presentation.itemText, true)
|
||||
presentation.itemText = parameterName
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleInsert(context: InsertionContext) {
|
||||
@@ -227,18 +235,26 @@ class ParameterNameAndTypeCompletion(
|
||||
val settings = CodeStyleSettingsManager.getInstance(context.project).currentSettings.getCustomSettings(KotlinCodeStyleSettings::class.java)
|
||||
val spaceBefore = if (settings.SPACE_BEFORE_TYPE_COLON) " " else ""
|
||||
val spaceAfter = if (settings.SPACE_AFTER_TYPE_COLON) " " else ""
|
||||
val text = parameterName + spaceBefore + ":" + spaceAfter
|
||||
|
||||
val startOffset = context.startOffset
|
||||
context.document.insertString(startOffset, text)
|
||||
if (shouldInsertType) {
|
||||
val text = parameterName + spaceBefore + ":" + spaceAfter
|
||||
context.document.insertString(startOffset, text)
|
||||
|
||||
// update start offset so that it does not include the text we inserted
|
||||
context.offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, startOffset + text.length)
|
||||
// update start offset so that it does not include the text we inserted
|
||||
context.offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, startOffset + text.length)
|
||||
|
||||
super.handleInsert(context)
|
||||
super.handleInsert(context)
|
||||
}
|
||||
else {
|
||||
context.document.replaceString(startOffset, context.tailOffset, parameterName)
|
||||
|
||||
context.commitDocument()
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?)
|
||||
= other is MyLookupElement && parameterName == other.parameterName && type == other.type
|
||||
= other is MyLookupElement && parameterName == other.parameterName && type == other.type && shouldInsertType == other.shouldInsertType
|
||||
override fun hashCode() = parameterName.hashCode()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
class Foo
|
||||
|
||||
lateinit var f<caret>
|
||||
|
||||
// EXIST: { itemText: "foo: Foo" }
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
class Foo
|
||||
|
||||
fun test() {
|
||||
var f<caret>
|
||||
}
|
||||
|
||||
// EXIST: { itemText: "foo", tailText: ": Foo (<root>)" }
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
class Foo
|
||||
|
||||
val f<caret>
|
||||
|
||||
// EXIST: { itemText: "foo", tailText: ": Foo (<root>)" }
|
||||
+27
@@ -2789,6 +2789,33 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/idea-completion/testData/basic/common/variableNameAndType")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class VariableNameAndType extends AbstractJSBasicCompletionTest {
|
||||
public void testAllFilesPresentInVariableNameAndType() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/variableNameAndType"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("Lateinit.kt")
|
||||
public void testLateinit() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/variableNameAndType/Lateinit.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("Local.kt")
|
||||
public void testLocal() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/variableNameAndType/Local.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("TopLevel.kt")
|
||||
public void testTopLevel() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/variableNameAndType/TopLevel.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/idea-completion/testData/basic/common/visibility")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
+27
@@ -2789,6 +2789,33 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/idea-completion/testData/basic/common/variableNameAndType")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class VariableNameAndType extends AbstractJvmBasicCompletionTest {
|
||||
public void testAllFilesPresentInVariableNameAndType() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/variableNameAndType"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("Lateinit.kt")
|
||||
public void testLateinit() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/variableNameAndType/Lateinit.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("Local.kt")
|
||||
public void testLocal() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/variableNameAndType/Local.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("TopLevel.kt")
|
||||
public void testTopLevel() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/variableNameAndType/TopLevel.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/idea-completion/testData/basic/common/visibility")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user