diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt index 48d037f2ae1..40248ba71ae 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt @@ -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("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("Prefix ends with uppercase letter") { diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt index d1aaf5994d4..f9f8da2388e 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt @@ -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) } } } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ParameterNameAndTypeCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/VariableOrParameterNameWithTypeCompletion.kt similarity index 88% rename from idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ParameterNameAndTypeCompletion.kt rename to idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/VariableOrParameterNameWithTypeCompletion.kt index 0c7e5178a82..28d4140c8c5 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ParameterNameAndTypeCompletion.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/VariableOrParameterNameWithTypeCompletion.kt @@ -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 private val classNamePrefixMatchers: List @@ -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(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() } diff --git a/idea/idea-completion/testData/basic/common/variableNameAndType/Lateinit.kt b/idea/idea-completion/testData/basic/common/variableNameAndType/Lateinit.kt new file mode 100644 index 00000000000..34fb827b966 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/variableNameAndType/Lateinit.kt @@ -0,0 +1,6 @@ + +class Foo + +lateinit var f + +// EXIST: { itemText: "foo: Foo" } \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/variableNameAndType/Local.kt b/idea/idea-completion/testData/basic/common/variableNameAndType/Local.kt new file mode 100644 index 00000000000..c791932f1f0 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/variableNameAndType/Local.kt @@ -0,0 +1,8 @@ + +class Foo + +fun test() { + var f +} + +// EXIST: { itemText: "foo", tailText: ": Foo ()" } \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/variableNameAndType/TopLevel.kt b/idea/idea-completion/testData/basic/common/variableNameAndType/TopLevel.kt new file mode 100644 index 00000000000..d5160d8ae6c --- /dev/null +++ b/idea/idea-completion/testData/basic/common/variableNameAndType/TopLevel.kt @@ -0,0 +1,6 @@ + +class Foo + +val f + +// EXIST: { itemText: "foo", tailText: ": Foo ()" } \ No newline at end of file diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java index d1dcd39b3e4..45f20a13d8b 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java @@ -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) diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java index c179f0cafc3..3d22a1cca0c 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java @@ -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)