KT-6608 Code completion does not work after $ in string literals

KT-5070 Add braces when complete function in string templates

 #KT-6608 Fixed
 #KT-5070 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-03-03 19:04:13 +03:00
parent c44519fa4e
commit ffff6226b0
24 changed files with 249 additions and 85 deletions
@@ -17,36 +17,38 @@
package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.completion.*
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.patterns.CharPattern
import com.intellij.patterns.PatternCondition
import com.intellij.patterns.StandardPatterns
import com.intellij.psi.PsiElement
import com.intellij.psi.search.DelegatingGlobalSearchScope
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.ProcessingContext
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.idea.caches.resolve.*
import org.jetbrains.kotlin.idea.caches.KotlinIndicesHelper
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletion
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
import org.jetbrains.kotlin.idea.caches.KotlinIndicesHelper
import com.intellij.openapi.project.Project
import com.intellij.psi.search.DelegatingGlobalSearchScope
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.load.java.descriptors.SamConstructorDescriptorKindExclude
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.calls.smartcasts.SmartCastUtils
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.idea.refactoring.comparePossiblyOverridingDescriptors
import kotlin.properties.Delegates
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.idea.util.makeNotNullable
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
import org.jetbrains.kotlin.idea.util.CallType
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindExclude
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import com.intellij.patterns.StandardPatterns
import com.intellij.util.ProcessingContext
import com.intellij.patterns.PatternCondition
import org.jetbrains.kotlin.idea.util.makeNotNullable
import org.jetbrains.kotlin.load.java.descriptors.SamConstructorDescriptorKindExclude
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo
import org.jetbrains.kotlin.resolve.calls.smartcasts.SmartCastUtils
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindExclude
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import kotlin.properties.Delegates
class CompletionSessionConfiguration(
val completeNonImportedDeclarations: Boolean,
@@ -88,18 +90,21 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess
protected val bindingContext: BindingContext? = expression?.let { resolutionFacade.analyze(it, BodyResolveMode.PARTIAL_FOR_COMPLETION) }
protected val inDescriptor: DeclarationDescriptor? = expression?.let { bindingContext!!.get(BindingContext.RESOLUTION_SCOPE, it)?.getContainingDeclaration() }
private fun singleCharPattern(char: Char): CharPattern {
return StandardPatterns.character().with(
object : PatternCondition<Char>(char.toString()) {
override fun accepts(c: Char, context: ProcessingContext) = c == char
})
}
private val kotlinIdentifierStartPattern = StandardPatterns.and(StandardPatterns.character().javaIdentifierStart(), StandardPatterns.not(singleCharPattern('$')))
private val kotlinIdentifierPartPattern = StandardPatterns.and(StandardPatterns.character().javaIdentifierPart(), StandardPatterns.not(singleCharPattern('$')))
protected val prefix: String = CompletionUtil.findIdentifierPrefix(
parameters.getPosition().getContainingFile(),
parameters.getOffset(),
StandardPatterns.or(StandardPatterns.character().javaIdentifierPart(),
StandardPatterns.character().with(
object : PatternCondition<Char>("@") {
override fun accepts(c: Char?, context: ProcessingContext?): Boolean {
return c == '@'
}
})
),
StandardPatterns.character().javaIdentifierStart())
StandardPatterns.or(kotlinIdentifierPartPattern, singleCharPattern('@')),
kotlinIdentifierStartPattern)
protected val resultSet: CompletionResultSet = resultSet
.withPrefixMatcher(prefix)
@@ -129,7 +134,8 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess
LookupElementFactory(receiverTypes)
}
protected val collector: LookupElementsCollector = LookupElementsCollector(prefixMatcher, parameters, resolutionFacade, lookupElementFactory)
protected val collector: LookupElementsCollector = LookupElementsCollector(
prefixMatcher, parameters, resolutionFacade, lookupElementFactory, expression?.getParent() is JetSimpleNameStringTemplateEntry)
protected val project: Project = position.getProject()
@@ -89,6 +89,20 @@ fun LookupElement.withReceiverCast(): LookupElement {
}
}
fun LookupElement.withBracesSurrounding(): LookupElement {
return object: LookupElementDecorator<LookupElement>(this) {
override fun handleInsert(context: InsertionContext) {
context.getDocument().insertString(context.getStartOffset(), "{")
val tailOffset = context.getTailOffset()
context.getDocument().insertString(tailOffset, "}")
context.setTailOffset(tailOffset)
super.handleInsert(context)
}
}
}
val KEEP_OLD_ARGUMENT_LIST_ON_TAB_KEY = Key<Unit>("KEEP_OLD_ARGUMENT_LIST_ON_TAB_KEY")
fun LookupElement.keepOldArgumentListOnTab(): LookupElement {
@@ -35,7 +35,8 @@ class LookupElementsCollector(
private val prefixMatcher: PrefixMatcher,
private val completionParameters: CompletionParameters,
private val resolutionFacade: ResolutionFacade,
private val lookupElementFactory: LookupElementFactory
private val lookupElementFactory: LookupElementFactory,
private val surroundCallsWithBraces: Boolean
) {
private val elements = ArrayList<LookupElement>()
@@ -62,9 +63,15 @@ class LookupElementsCollector(
public fun addDescriptorElements(descriptor: DeclarationDescriptor, suppressAutoInsertion: Boolean, withReceiverCast: Boolean) {
run {
var lookupElement = lookupElementFactory.createLookupElement(resolutionFacade, descriptor, true)
if (withReceiverCast) {
lookupElement = lookupElement.withReceiverCast()
}
if (surroundCallsWithBraces && descriptor is FunctionDescriptor) {
lookupElement = lookupElement.withBracesSurrounding()
}
if (suppressAutoInsertion) {
addElementWithAutoInsertionSuppressed(lookupElement)
}
@@ -81,8 +88,9 @@ class LookupElementsCollector(
if (KotlinBuiltIns.isFunctionOrExtensionFunctionType(parameterType)) {
val parameterCount = KotlinBuiltIns.getParameterTypeProjectionsFromFunctionType(parameterType).size()
if (parameterCount > 1) {
val lookupElement = lookupElementFactory.createLookupElement(resolutionFacade, descriptor, true)
addElement(object : LookupElementDecorator<LookupElement>(lookupElement) {
var lookupElement = lookupElementFactory.createLookupElement(resolutionFacade, descriptor, true)
lookupElement = object : LookupElementDecorator<LookupElement>(lookupElement) {
override fun renderElement(presentation: LookupElementPresentation) {
super.renderElement(presentation)
@@ -95,7 +103,13 @@ class LookupElementsCollector(
override fun handleInsert(context: InsertionContext) {
KotlinFunctionInsertHandler(CaretPosition.IN_BRACKETS, GenerateLambdaInfo(parameterType, true)).handleInsert(context, this)
}
})
}
if (surroundCallsWithBraces) {
lookupElement = lookupElement.withBracesSurrounding()
}
addElement(lookupElement)
}
}
}
@@ -0,0 +1,6 @@
fun foo(param: String) {
val s = "$<caret>"
}
// EXIST: foo
// EXIST: param
@@ -0,0 +1,6 @@
fun foo(param: String) {
val s = "${<caret>}"
}
// EXIST: foo
// EXIST: param
@@ -0,0 +1,5 @@
fun foo(): String {
val s = "$<caret>"
}
// ELEMENT: foo
@@ -0,0 +1,5 @@
fun foo(): String {
val s = "${foo()<caret>}"
}
// ELEMENT: foo
@@ -0,0 +1,5 @@
fun foo(p: String): String {
val s = "$<caret>"
}
// ELEMENT: foo
@@ -0,0 +1,5 @@
fun foo(p: String): String {
val s = "${foo(<caret>)}"
}
// ELEMENT: foo
@@ -0,0 +1,5 @@
fun foo(): String {
val s = "${<caret>}"
}
// ELEMENT: foo
@@ -0,0 +1,5 @@
fun foo(): String {
val s = "${foo()<caret>}"
}
// ELEMENT: foo
@@ -0,0 +1,5 @@
fun foo(param: Any): String {
val s = "${<caret>}"
}
// ELEMENT: param
@@ -0,0 +1,5 @@
fun foo(param: Any): String {
val s = "${param<caret>}"
}
// ELEMENT: param
@@ -0,0 +1,7 @@
fun bar() {}
fun test(){
"$b<caret>"
}
// ELEMENT: bar
@@ -0,0 +1,7 @@
fun bar() {}
fun test(){
"${bar()<caret>}"
}
// ELEMENT: bar
@@ -0,0 +1,6 @@
fun foo(): String {
val s = "$<caret>xxx"
}
// ELEMENT: foo
// CHAR: \t
@@ -0,0 +1,6 @@
fun foo(): String {
val s = "${foo()<caret>}"
}
// ELEMENT: foo
// CHAR: \t
@@ -883,6 +883,18 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
doTest(fileName);
}
@TestMetadata("StringTemplate1.kt")
public void testStringTemplate1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/StringTemplate1.kt");
doTest(fileName);
}
@TestMetadata("StringTemplate2.kt")
public void testStringTemplate2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/StringTemplate2.kt");
doTest(fileName);
}
@TestMetadata("SubpackageInFun.kt")
public void testSubpackageInFun() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/SubpackageInFun.kt");
@@ -883,6 +883,18 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
doTest(fileName);
}
@TestMetadata("StringTemplate1.kt")
public void testStringTemplate1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/StringTemplate1.kt");
doTest(fileName);
}
@TestMetadata("StringTemplate2.kt")
public void testStringTemplate2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/StringTemplate2.kt");
doTest(fileName);
}
@TestMetadata("SubpackageInFun.kt")
public void testSubpackageInFun() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/SubpackageInFun.kt");
@@ -17,11 +17,12 @@
package org.jetbrains.kotlin.completion.handlers
import com.intellij.codeInsight.completion.CompletionType
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.idea.JetLightProjectDescriptor
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import java.io.File
public abstract class AbstractCompletionHandlerTest : CompletionHandlerTestBase() {
public abstract class AbstractCompletionHandlerTest(private val defaultCompletionType: CompletionType) : CompletionHandlerTestBase() {
private val INVOCATION_COUNT_PREFIX = "INVOCATION_COUNT:"
private val LOOKUP_STRING_PREFIX = "ELEMENT:"
private val ELEMENT_TEXT_PREFIX = "ELEMENT_TEXT:"
@@ -53,32 +54,20 @@ public abstract class AbstractCompletionHandlerTest : CompletionHandlerTestBase(
else -> error("Unknown completion type: $completionTypeString")
}
doTestWithTextLoaded(completionType, invocationCount, lookupString, itemText, tailText, completionChar)
doTestWithTextLoaded(completionType, invocationCount, lookupString, itemText, tailText, completionChar, testPath + ".after")
}
protected open fun setUpFixture(testPath: String) {
fixture.configureByFile(testPath)
}
protected abstract val defaultCompletionType: CompletionType
override fun getProjectDescriptor() = JetLightProjectDescriptor.INSTANCE
}
public abstract class AbstractBasicCompletionHandlerTest() : AbstractCompletionHandlerTest() {
override val defaultCompletionType: CompletionType = CompletionType.BASIC
override val testDataRelativePath: String = "/completion/handlers/basic"
}
public abstract class AbstractBasicCompletionHandlerTest() : AbstractCompletionHandlerTest(CompletionType.BASIC)
public abstract class AbstractSmartCompletionHandlerTest() : AbstractCompletionHandlerTest() {
override val defaultCompletionType: CompletionType = CompletionType.SMART
override val testDataRelativePath: String = "/completion/handlers/smart"
}
public abstract class AbstractSmartCompletionHandlerTest() : AbstractCompletionHandlerTest(CompletionType.SMART)
public abstract class AbstractCompletionCharFilterTest() : AbstractCompletionHandlerTest() {
override val defaultCompletionType: CompletionType = CompletionType.BASIC
override val testDataRelativePath: String = "/completion/handlers/charFilter"
}
public abstract class AbstractCompletionCharFilterTest() : AbstractCompletionHandlerTest(CompletionType.BASIC)
public abstract class AbstractKeywordCompletionHandlerTest() : AbstractCompletionHandlerTest() {
override val defaultCompletionType: CompletionType = CompletionType.BASIC
override val testDataRelativePath: String = "/completion/handlers/keywords"
}
public abstract class AbstractKeywordCompletionHandlerTest() : AbstractCompletionHandlerTest(CompletionType.BASIC)
@@ -17,12 +17,18 @@
package org.jetbrains.kotlin.completion.handlers
import com.intellij.codeInsight.completion.CompletionType
import org.jetbrains.kotlin.idea.formatter.JetCodeStyleSettings
import com.intellij.psi.codeStyle.CodeStyleSettingsManager
import org.jetbrains.kotlin.idea.PluginTestCaseBase
import org.jetbrains.kotlin.idea.formatter.JetCodeStyleSettings
import java.io.File
deprecated("All tests from here to be moved to the generated test")
public class BasicCompletionHandlerTest : CompletionHandlerTestBase(){
override val testDataRelativePath: String = "/completion/handlers"
private fun checkResult(){
fixture.checkResultByFile(getTestName(false) + ".kt.after")
}
override fun getTestDataPath() = File(PluginTestCaseBase.getTestDataPathBase(), "/completion/handlers").getPath() + File.separator
private fun doTest() {
doTest(2, "*", null, null, '\n')
@@ -34,7 +40,7 @@ public class BasicCompletionHandlerTest : CompletionHandlerTestBase(){
private fun doTest(time: Int, lookupString: String?, itemText: String?, tailText: String?, completionChar: Char) {
fixture.configureByFile(fileName())
doTestWithTextLoaded(CompletionType.BASIC, time, lookupString, itemText, tailText, completionChar)
doTestWithTextLoaded(CompletionType.BASIC, time, lookupString, itemText, tailText, completionChar, fileName() + ".after")
}
fun testClassCompletionImport() = doTest(2, "SortedSet", null, '\n')
@@ -30,6 +30,9 @@ import java.util.regex.Pattern;
@SuppressWarnings("all")
@TestMetadata("idea/testData/completion/handlers/basic")
@TestDataPath("$PROJECT_ROOT")
@InnerTestClasses({
BasicCompletionHandlerTestGenerated.StringTemplate.class,
})
@RunWith(JUnit3RunnerWithInners.class)
public class BasicCompletionHandlerTestGenerated extends AbstractBasicCompletionHandlerTest {
public void testAllFilesPresentInBasic() throws Exception {
@@ -77,4 +80,49 @@ public class BasicCompletionHandlerTestGenerated extends AbstractBasicCompletion
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/handlers/basic/SuperTypeArg.kt");
doTest(fileName);
}
@TestMetadata("idea/testData/completion/handlers/basic/stringTemplate")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class StringTemplate extends AbstractBasicCompletionHandlerTest {
@TestMetadata("1.kt")
public void test1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/handlers/basic/stringTemplate/1.kt");
doTest(fileName);
}
@TestMetadata("2.kt")
public void test2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/handlers/basic/stringTemplate/2.kt");
doTest(fileName);
}
@TestMetadata("3.kt")
public void test3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/handlers/basic/stringTemplate/3.kt");
doTest(fileName);
}
@TestMetadata("4.kt")
public void test4() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/handlers/basic/stringTemplate/4.kt");
doTest(fileName);
}
public void testAllFilesPresentInStringTemplate() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/completion/handlers/basic/stringTemplate"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("NotEmptyPrefix.kt")
public void testNotEmptyPrefix() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/handlers/basic/stringTemplate/NotEmptyPrefix.kt");
doTest(fileName);
}
@TestMetadata("Replace.kt")
public void testReplace() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/handlers/basic/stringTemplate/Replace.kt");
doTest(fileName);
}
}
}
@@ -16,23 +16,20 @@
package org.jetbrains.kotlin.completion.handlers
import org.jetbrains.kotlin.idea.JetLightCodeInsightFixtureTestCase
import com.intellij.codeInsight.completion.CompletionType
import com.intellij.codeInsight.lookup.LookupElement
import java.io.File
import org.jetbrains.kotlin.idea.PluginTestCaseBase
import com.intellij.codeInsight.lookup.impl.LookupImpl
import com.intellij.codeInsight.lookup.LookupEvent
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.codeInsight.lookup.LookupManager
import com.intellij.codeInsight.lookup.LookupElementPresentation
import org.junit.Assert
import com.intellij.codeInsight.lookup.LookupEvent
import com.intellij.codeInsight.lookup.LookupManager
import com.intellij.codeInsight.lookup.impl.LookupImpl
import com.intellij.openapi.application.Result
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import org.jetbrains.kotlin.idea.JetLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.test.JetTestUtils
import org.junit.Assert
public abstract class CompletionHandlerTestBase() : JetLightCodeInsightFixtureTestCase() {
protected abstract val testDataRelativePath: String
protected val fixture: JavaCodeInsightTestFixture
get() = myFixture
@@ -42,7 +39,8 @@ public abstract class CompletionHandlerTestBase() : JetLightCodeInsightFixtureTe
lookupString: String?,
itemText: String?,
tailText: String?,
completionChar: Char
completionChar: Char,
afterFilePath: String
) {
fixture.complete(completionType, time)
@@ -53,11 +51,7 @@ public abstract class CompletionHandlerTestBase() : JetLightCodeInsightFixtureTe
}
}
checkResult()
}
protected fun checkResult(){
fixture.checkResultByFile(afterFileName())
fixture.checkResultByFile(afterFilePath)
}
private fun getExistentLookupElement(lookupString: String?, itemText: String?, tailText: String?): LookupElement? {
@@ -111,13 +105,7 @@ public abstract class CompletionHandlerTestBase() : JetLightCodeInsightFixtureTe
return foundElement
}
protected fun afterFileName(): String = getTestName(false) + ".kt.after"
protected override fun getTestDataPath() : String = File(PluginTestCaseBase.getTestDataPathBase(), testDataRelativePath).getPath() + File.separator
protected fun selectItem(item: LookupElement?) {
selectItem(item, 0.toChar())
}
override fun getTestDataPath() = JetTestUtils.getHomeDirectory()
protected fun selectItem(item: LookupElement?, completionChar: Char) {
val lookup = (fixture.getLookup() as LookupImpl)
@@ -78,13 +78,10 @@ public abstract class AbstractCodeFragmentCompletionTest : AbstractJvmBasicCompl
}
}
public abstract class AbstractCodeFragmentCompletionHandlerTest : AbstractCompletionHandlerTest() {
public abstract class AbstractCodeFragmentCompletionHandlerTest : AbstractCompletionHandlerTest(CompletionType.BASIC) {
override fun setUpFixture(testPath: String) {
myFixture.configureByCodeFragment(testPath)
}
override val testDataRelativePath: String = "/completion/handlers/runtimeCast/"
override val defaultCompletionType: CompletionType = CompletionType.BASIC
}
private fun JavaCodeInsightTestFixture.configureByCodeFragment(filePath: String) {