diff --git a/idea/src/org/jetbrains/jet/plugin/completion/JetCompletionContributor.kt b/idea/src/org/jetbrains/jet/plugin/completion/JetCompletionContributor.kt index 621b5117efd..417b34f4276 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/JetCompletionContributor.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/JetCompletionContributor.kt @@ -31,17 +31,23 @@ import org.jetbrains.jet.plugin.completion.smart.SmartCompletion import com.intellij.patterns.PsiJavaPatterns.elementType import com.intellij.patterns.PsiJavaPatterns.psiElement import com.intellij.psi.PsiElement -import com.intellij.psi.PsiComment -import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.tree.TokenSet +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.TokenType +import org.jetbrains.jet.lang.psi.JetNameReferenceExpression +import org.jetbrains.jet.lang.psi.JetUserType import org.jetbrains.jet.lang.psi.JetTypeReference +import org.jetbrains.jet.lang.psi.JetNamedFunction +import org.jetbrains.jet.lang.psi.JetProperty +import org.jetbrains.jet.lang.psi.JetPsiFactory +import com.intellij.psi.PsiErrorElement +import com.intellij.psi.search.PsiElementProcessor public class JetCompletionContributor : CompletionContributor() { private val AFTER_NUMBER_LITERAL = psiElement().afterLeafSkipping(psiElement().withText(""), psiElement().withElementType(elementType().oneOf(JetTokens.FLOAT_LITERAL, JetTokens.INTEGER_LITERAL))) private val AFTER_INTEGER_LITERAL_AND_DOT = psiElement().afterLeafSkipping(psiElement().withText("."), psiElement().withElementType(elementType().oneOf(JetTokens.INTEGER_LITERAL))) - - private val EXTENSION_RECEIVER_TYPE_DUMMY_IDENTIFIER = "KotlinExtensionDummy.fake() {}" // A way to add reference into file at completion place - private val EXTENSION_RECEIVER_TYPE_ACTIVATION_PATTERN = psiElement().afterLeaf(JetTokens.FUN_KEYWORD.toString(), JetTokens.VAL_KEYWORD.toString(), JetTokens.VAR_KEYWORD.toString()) + private val EXTENSION_RECEIVER_TYPE_PATTERN = psiElement().afterLeaf(JetTokens.FUN_KEYWORD.toString(), JetTokens.VAL_KEYWORD.toString(), JetTokens.VAR_KEYWORD.toString()) private val DEFAULT_DUMMY_IDENTIFIER = CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + "$" // add '$' to ignore context after the caret @@ -67,11 +73,7 @@ public class JetCompletionContributor : CompletionContributor() { PackageDirectiveCompletion.ACTIVATION_PATTERN.accepts(tokenBefore) -> PackageDirectiveCompletion.DUMMY_IDENTIFIER - EXTENSION_RECEIVER_TYPE_ACTIVATION_PATTERN.accepts(tokenBefore) -> EXTENSION_RECEIVER_TYPE_DUMMY_IDENTIFIER - - tokenBefore != null && isExtensionReceiverAfterDot(tokenBefore) -> CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + "." - - else -> DEFAULT_DUMMY_IDENTIFIER + else -> specialExtensionReceiverDummyIdentifier(tokenBefore) ?: DEFAULT_DUMMY_IDENTIFIER } context.setDummyIdentifier(dummyIdentifier) @@ -109,23 +111,46 @@ public class JetCompletionContributor : CompletionContributor() { } } - private val declarationKeywords = setOf(JetTokens.FUN_KEYWORD, JetTokens.VAL_KEYWORD, JetTokens.VAR_KEYWORD) + private val declarationKeywords = TokenSet.create(JetTokens.FUN_KEYWORD, JetTokens.VAL_KEYWORD, JetTokens.VAR_KEYWORD) + private val declarationTokens = TokenSet.orSet(TokenSet.create(JetTokens.IDENTIFIER, JetTokens.LT, JetTokens.GT, + JetTokens.COMMA, JetTokens.DOT, JetTokens.QUEST, JetTokens.COLON, + JetTokens.IN_KEYWORD, JetTokens.OUT_KEYWORD, + JetTokens.LPAR, JetTokens.RPAR, JetTokens.ARROW, + TokenType.ERROR_ELEMENT), + JetTokens.WHITE_SPACE_OR_COMMENT_BIT_SET) - private fun isExtensionReceiverAfterDot(tokenBefore: PsiElement): Boolean { - var prev = tokenBefore.getPrevSibling() - if (tokenBefore.getNode()!!.getElementType() != JetTokens.DOT) { - if (prev == null || prev!!.getNode()!!.getElementType() != JetTokens.DOT) return false - prev = prev!!.getPrevSibling() - } + private fun specialExtensionReceiverDummyIdentifier(tokenBefore: PsiElement?): String? { + var token = tokenBefore ?: return null + var ltCount = 0 + var gtCount = 0 + val builder = StringBuilder() + while (true) { + val tokenType = token.getNode()!!.getElementType() + if (tokenType in declarationKeywords) { + val balance = ltCount - gtCount + if (balance < 0) return null + builder.append(token.getText()!!.reverse()) + builder.reverse() - while (prev != null) { - if (prev!!.getNode()!!.getElementType() in declarationKeywords) { - return true + var tail = "X" + ">".repeat(balance) + ".f" + if (tokenType == JetTokens.FUN_KEYWORD) { + tail += "()" + } + builder append tail + + val text = builder.toString() + val file = JetPsiFactory(tokenBefore.getProject()).createFile(text) + val declaration = file.getDeclarations().singleOrNull() ?: return null + if (declaration.getTextLength() != text.length) return null + val containsErrorElement = !PsiTreeUtil.processElements(file, PsiElementProcessor{ it !is PsiErrorElement }) + return if (containsErrorElement) null else tail + "$" } - if (prev !is PsiComment && prev !is PsiWhiteSpace && prev !is JetTypeReference) return false - prev = prev!!.getPrevSibling() + if (tokenType !in declarationTokens) return null + if (tokenType == JetTokens.LT) ltCount++ + if (tokenType == JetTokens.GT) gtCount++ + builder.append(token.getText()!!.reverse()) + token = PsiTreeUtil.prevLeaf(token) ?: return null } - return false } private fun performCompletion(parameters: CompletionParameters, result: CompletionResultSet) { @@ -175,7 +200,7 @@ public class JetCompletionContributor : CompletionContributor() { if (invocationCount == 0 && prefixMatcher.getPrefix().isEmpty() && AFTER_INTEGER_LITERAL_AND_DOT.accepts(position)) return true // no auto-popup on typing after "val", "var" and "fun" - if (EXTENSION_RECEIVER_TYPE_ACTIVATION_PATTERN.accepts(position) && invocationCount == 0) return true + if (invocationCount == 0 && EXTENSION_RECEIVER_TYPE_PATTERN.accepts(position)) return true return false } diff --git a/idea/testData/completion/basic/common/GenericExtensionFunReceiver1.kt b/idea/testData/completion/basic/common/GenericExtensionFunReceiver1.kt new file mode 100644 index 00000000000..570a5eb66a1 --- /dev/null +++ b/idea/testData/completion/basic/common/GenericExtensionFunReceiver1.kt @@ -0,0 +1,8 @@ +fun foo() {} + +fun S + +// INVOCATION_COUNT: 1 +// EXIST: String +// EXIST: Set +// ABSENT: foo diff --git a/idea/testData/completion/basic/common/GenericExtensionFunReceiver2.kt b/idea/testData/completion/basic/common/GenericExtensionFunReceiver2.kt new file mode 100644 index 00000000000..0668a823c0b --- /dev/null +++ b/idea/testData/completion/basic/common/GenericExtensionFunReceiver2.kt @@ -0,0 +1,11 @@ +class Foo{} + +fun foo() {} + +fun + +// INVOCATION_COUNT: 1 +// EXIST: Foo +// EXIST: String +// EXIST: Set +// ABSENT: foo diff --git a/idea/testData/completion/basic/common/GenericExtensionFunTypeArgument.kt b/idea/testData/completion/basic/common/GenericExtensionFunTypeArgument.kt new file mode 100644 index 00000000000..e29318bdf95 --- /dev/null +++ b/idea/testData/completion/basic/common/GenericExtensionFunTypeArgument.kt @@ -0,0 +1,8 @@ +fun Strange(){} + +fun Annotations + +// INVOCATION_COUNT: 1 +// EXIST: String +// EXIST: Set +// ABSENT: Strange diff --git a/idea/testData/completion/basic/common/GenericExtensionFunTypeArgument2.kt b/idea/testData/completion/basic/common/GenericExtensionFunTypeArgument2.kt new file mode 100644 index 00000000000..2ae1c42eca3 --- /dev/null +++ b/idea/testData/completion/basic/common/GenericExtensionFunTypeArgument2.kt @@ -0,0 +1,7 @@ +fun Strange(){} + +fun Map<() -> Unit, Str + +// INVOCATION_COUNT: 0 +// EXIST: String +// ABSENT: Strange diff --git a/idea/tests/org/jetbrains/jet/completion/JSBasicCompletionTestGenerated.java b/idea/tests/org/jetbrains/jet/completion/JSBasicCompletionTestGenerated.java index acd228da9ca..34c7f415fe9 100644 --- a/idea/tests/org/jetbrains/jet/completion/JSBasicCompletionTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/completion/JSBasicCompletionTestGenerated.java @@ -17,10 +17,13 @@ package org.jetbrains.jet.completion; import com.intellij.testFramework.TestDataPath; +import junit.framework.Test; +import junit.framework.TestSuite; +import org.junit.runner.RunWith; import org.jetbrains.jet.JetTestUtils; import org.jetbrains.jet.test.InnerTestClasses; import org.jetbrains.jet.test.TestMetadata; -import org.junit.runner.RunWith; +import org.jetbrains.jet.JUnit3RunnerWithInners; import java.io.File; import java.util.regex.Pattern; @@ -225,6 +228,30 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes doTest(fileName); } + @TestMetadata("GenericExtensionFunReceiver1.kt") + public void testGenericExtensionFunReceiver1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/GenericExtensionFunReceiver1.kt"); + doTest(fileName); + } + + @TestMetadata("GenericExtensionFunReceiver2.kt") + public void testGenericExtensionFunReceiver2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/GenericExtensionFunReceiver2.kt"); + doTest(fileName); + } + + @TestMetadata("GenericExtensionFunTypeArgument.kt") + public void testGenericExtensionFunTypeArgument() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/GenericExtensionFunTypeArgument.kt"); + doTest(fileName); + } + + @TestMetadata("GenericExtensionFunTypeArgument2.kt") + public void testGenericExtensionFunTypeArgument2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/GenericExtensionFunTypeArgument2.kt"); + doTest(fileName); + } + @TestMetadata("HigherOrderFunction1.kt") public void testHigherOrderFunction1() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/HigherOrderFunction1.kt"); diff --git a/idea/tests/org/jetbrains/jet/completion/JvmBasicCompletionTestGenerated.java b/idea/tests/org/jetbrains/jet/completion/JvmBasicCompletionTestGenerated.java index ede4a5ef0b0..000338ca1fa 100644 --- a/idea/tests/org/jetbrains/jet/completion/JvmBasicCompletionTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/completion/JvmBasicCompletionTestGenerated.java @@ -225,6 +225,30 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT doTest(fileName); } + @TestMetadata("GenericExtensionFunReceiver1.kt") + public void testGenericExtensionFunReceiver1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/GenericExtensionFunReceiver1.kt"); + doTest(fileName); + } + + @TestMetadata("GenericExtensionFunReceiver2.kt") + public void testGenericExtensionFunReceiver2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/GenericExtensionFunReceiver2.kt"); + doTest(fileName); + } + + @TestMetadata("GenericExtensionFunTypeArgument.kt") + public void testGenericExtensionFunTypeArgument() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/GenericExtensionFunTypeArgument.kt"); + doTest(fileName); + } + + @TestMetadata("GenericExtensionFunTypeArgument2.kt") + public void testGenericExtensionFunTypeArgument2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/GenericExtensionFunTypeArgument2.kt"); + doTest(fileName); + } + @TestMetadata("HigherOrderFunction1.kt") public void testHigherOrderFunction1() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/HigherOrderFunction1.kt");