KT-13220: Add completion for variable names

Using parameter name completion

#KT-13220 fixed
This commit is contained in:
Simon Ogorodnik
2017-10-17 00:17:27 +03:00
parent e65adeb029
commit 7a991ddc25
8 changed files with 134 additions and 17 deletions
@@ -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") {
@@ -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)
}
}
}
@@ -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>)" }
@@ -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)
@@ -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)