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) {
|
if (declaration != null) {
|
||||||
completeDeclarationNameFromUnresolvedOrOverride(declaration)
|
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
|
// 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) {
|
if (parameters.invocationCount == 0) {
|
||||||
val suppressOtherCompletion = when (declaration) {
|
val suppressOtherCompletion = when (declaration) {
|
||||||
@@ -475,6 +479,29 @@ class BasicCompletionSession(
|
|||||||
else -> null
|
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 {
|
private val KEYWORDS_ONLY = object : CompletionKind {
|
||||||
@@ -641,13 +668,13 @@ class BasicCompletionSession(
|
|||||||
|
|
||||||
override fun addWeighers(sorter: CompletionSorter): CompletionSorter {
|
override fun addWeighers(sorter: CompletionSorter): CompletionSorter {
|
||||||
if (shouldCompleteParameterNameAndType()) {
|
if (shouldCompleteParameterNameAndType()) {
|
||||||
return sorter.weighBefore("prefix", ParameterNameAndTypeCompletion.Weigher)
|
return sorter.weighBefore("prefix", VariableOrParameterNameWithTypeCompletion.Weigher)
|
||||||
}
|
}
|
||||||
return sorter
|
return sorter
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun completeParameterNameAndType() {
|
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)
|
// 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") {
|
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) {
|
if (tokenAt.node.elementType == KtTokens.IDENTIFIER) {
|
||||||
val parameter = tokenAt.parent as? KtParameter
|
val parameter = tokenAt.parent as? KtParameter
|
||||||
if (parameter != null) {
|
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 org.jetbrains.kotlin.types.isError
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class ParameterNameAndTypeCompletion(
|
class VariableOrParameterNameWithTypeCompletion(
|
||||||
private val collector: LookupElementsCollector,
|
private val collector: LookupElementsCollector,
|
||||||
private val lookupElementFactory: BasicLookupElementFactory,
|
private val lookupElementFactory: BasicLookupElementFactory,
|
||||||
private val prefixMatcher: PrefixMatcher,
|
private val prefixMatcher: PrefixMatcher,
|
||||||
private val resolutionFacade: ResolutionFacade
|
private val resolutionFacade: ResolutionFacade,
|
||||||
|
private val withType: Boolean
|
||||||
) {
|
) {
|
||||||
private val userPrefixes: List<String>
|
private val userPrefixes: List<String>
|
||||||
private val classNamePrefixMatchers: List<PrefixMatcher>
|
private val classNamePrefixMatchers: List<PrefixMatcher>
|
||||||
@@ -114,7 +115,7 @@ class ParameterNameAndTypeCompletion(
|
|||||||
if (descriptor != null) {
|
if (descriptor != null) {
|
||||||
val parameterType = descriptor.type
|
val parameterType = descriptor.type
|
||||||
if (parameterType.isVisible(visibilityFilter)) {
|
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
|
val count = lookupElementToCount[lookupElement] ?: 0
|
||||||
lookupElementToCount[lookupElement] = count + 1
|
lookupElementToCount[lookupElement] = count + 1
|
||||||
}
|
}
|
||||||
@@ -144,7 +145,7 @@ class ParameterNameAndTypeCompletion(
|
|||||||
for (name in nameSuggestions) {
|
for (name in nameSuggestions) {
|
||||||
val parameterName = userPrefix + name
|
val parameterName = userPrefix + name
|
||||||
if (prefixMatcher.isStartMatch(parameterName)) {
|
if (prefixMatcher.isStartMatch(parameterName)) {
|
||||||
val lookupElement = MyLookupElement.create(parameterName, type, lookupElementFactory)
|
val lookupElement = MyLookupElement.create(parameterName, type, withType, lookupElementFactory)
|
||||||
if (lookupElement != null) {
|
if (lookupElement != null) {
|
||||||
lookupElement.putUserData(PRIORITY_KEY, userPrefix.length) // suggestions with longer user prefix get lower priority
|
lookupElement.putUserData(PRIORITY_KEY, userPrefix.length) // suggestions with longer user prefix get lower priority
|
||||||
collector.addElement(lookupElement, notImported)
|
collector.addElement(lookupElement, notImported)
|
||||||
@@ -185,13 +186,14 @@ class ParameterNameAndTypeCompletion(
|
|||||||
private class MyLookupElement private constructor(
|
private class MyLookupElement private constructor(
|
||||||
private val parameterName: String,
|
private val parameterName: String,
|
||||||
private val type: Type,
|
private val type: Type,
|
||||||
typeLookupElement: LookupElement
|
typeLookupElement: LookupElement,
|
||||||
|
private val shouldInsertType: Boolean
|
||||||
) : LookupElementDecorator<LookupElement>(typeLookupElement) {
|
) : LookupElementDecorator<LookupElement>(typeLookupElement) {
|
||||||
|
|
||||||
companion object {
|
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 typeLookupElement = type.createTypeLookupElement(factory) ?: return null
|
||||||
val lookupElement = MyLookupElement(parameterName, type, typeLookupElement)
|
val lookupElement = MyLookupElement(parameterName, type, typeLookupElement, shouldInsertType)
|
||||||
return lookupElement.suppressAutoInsertion()
|
return lookupElement.suppressAutoInsertion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,7 +206,13 @@ class ParameterNameAndTypeCompletion(
|
|||||||
|
|
||||||
override fun renderElement(presentation: LookupElementPresentation) {
|
override fun renderElement(presentation: LookupElementPresentation) {
|
||||||
super.renderElement(presentation)
|
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) {
|
override fun handleInsert(context: InsertionContext) {
|
||||||
@@ -227,18 +235,26 @@ class ParameterNameAndTypeCompletion(
|
|||||||
val settings = CodeStyleSettingsManager.getInstance(context.project).currentSettings.getCustomSettings(KotlinCodeStyleSettings::class.java)
|
val settings = CodeStyleSettingsManager.getInstance(context.project).currentSettings.getCustomSettings(KotlinCodeStyleSettings::class.java)
|
||||||
val spaceBefore = if (settings.SPACE_BEFORE_TYPE_COLON) " " else ""
|
val spaceBefore = if (settings.SPACE_BEFORE_TYPE_COLON) " " else ""
|
||||||
val spaceAfter = if (settings.SPACE_AFTER_TYPE_COLON) " " else ""
|
val spaceAfter = if (settings.SPACE_AFTER_TYPE_COLON) " " else ""
|
||||||
val text = parameterName + spaceBefore + ":" + spaceAfter
|
|
||||||
val startOffset = context.startOffset
|
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
|
// update start offset so that it does not include the text we inserted
|
||||||
context.offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, startOffset + text.length)
|
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?)
|
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()
|
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")
|
@TestMetadata("idea/idea-completion/testData/basic/common/visibility")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@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")
|
@TestMetadata("idea/idea-completion/testData/basic/common/visibility")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user