diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt index fb79ca7b357..5b421812ec7 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt @@ -16,35 +16,26 @@ package org.jetbrains.kotlin.psi.psiUtil -import org.jetbrains.kotlin.psi.* -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile -import com.intellij.psi.util.PsiTreeUtil -import org.jetbrains.kotlin.lexer.JetTokens -import java.util.Collections import com.intellij.extapi.psi.StubBasedPsiElementBase -import java.util.ArrayList -import kotlin.test.assertTrue -import com.intellij.psi.search.SearchScope -import com.intellij.psi.search.PsiSearchScopeUtil -import com.intellij.psi.PsiParameterList -import com.intellij.psi.PsiParameter -import com.intellij.psi.PsiPackage -import com.intellij.psi.JavaDirectoryService -import com.intellij.psi.PsiDirectory -import org.jetbrains.kotlin.psi.stubs.KotlinClassOrObjectStub -import org.jetbrains.kotlin.types.expressions.OperatorConventions -import org.jetbrains.kotlin.diagnostics.DiagnosticUtils import com.intellij.lang.ASTNode -import com.intellij.openapi.util.Condition -import com.intellij.psi.PsiWhiteSpace -import com.intellij.psi.PsiComment -import org.jetbrains.kotlin.resolve.calls.CallTransformer.CallForImplicitInvoke import com.intellij.openapi.util.TextRange +import com.intellij.psi.* +import com.intellij.psi.search.PsiSearchScopeUtil +import com.intellij.psi.search.SearchScope import com.intellij.psi.stubs.StubElement +import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.kotlin.JetNodeTypes +import org.jetbrains.kotlin.diagnostics.DiagnosticUtils +import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.stubs.KotlinClassOrObjectStub +import org.jetbrains.kotlin.resolve.calls.CallTransformer.CallForImplicitInvoke +import org.jetbrains.kotlin.types.expressions.OperatorConventions import org.jetbrains.kotlin.utils.addToStdlib.lastIsInstanceOrNull +import java.util.ArrayList +import java.util.Collections +import kotlin.test.assertTrue public fun JetCallElement.getCallNameExpression(): JetSimpleNameExpression? { val calleeExpression = getCalleeExpression() ?: return null @@ -419,25 +410,17 @@ public fun PsiElement.prevLeaf(skipEmptyElements: Boolean = false): PsiElement? public fun PsiElement.nextLeaf(skipEmptyElements: Boolean = false): PsiElement? = PsiTreeUtil.nextLeaf(this, skipEmptyElements) -public fun PsiElement.prevLeafSkipWhitespacesAndComments(): PsiElement? { +public fun PsiElement.prevLeaf(filter: (PsiElement) -> Boolean): PsiElement? { var leaf = prevLeaf() - while (leaf is PsiWhiteSpace || leaf is PsiComment) { + while (leaf != null && !filter(leaf)) { leaf = leaf.prevLeaf() } return leaf } -public fun PsiElement.prevLeafSkipWhitespaces(): PsiElement? { - var leaf = prevLeaf() - while (leaf is PsiWhiteSpace) { - leaf = leaf.prevLeaf() - } - return leaf -} - -public fun PsiElement.nextLeafSkipWhitespacesAndComments(): PsiElement? { +public fun PsiElement.nextLeaf(filter: (PsiElement) -> Boolean): PsiElement? { var leaf = nextLeaf() - while (leaf is PsiWhiteSpace || leaf is PsiComment) { + while (leaf != null && !filter(leaf)) { leaf = leaf.nextLeaf() } return leaf diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt index da7c21cb7cf..108cbe84314 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt @@ -19,8 +19,10 @@ package org.jetbrains.kotlin.idea.completion import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.openapi.project.Project +import com.intellij.psi.PsiComment import com.intellij.psi.PsiElement import com.intellij.psi.PsiErrorElement +import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.filters.* import com.intellij.psi.filters.position.LeftNeighbour import com.intellij.psi.filters.position.PositionElementFilter @@ -31,7 +33,8 @@ import org.jetbrains.kotlin.lexer.JetKeywordToken import org.jetbrains.kotlin.lexer.JetTokens.* import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType -import org.jetbrains.kotlin.psi.psiUtil.prevLeafSkipWhitespacesAndComments +import org.jetbrains.kotlin.psi.psiUtil.nextLeaf +import org.jetbrains.kotlin.psi.psiUtil.prevLeaf import org.jetbrains.kotlin.psi.psiUtil.siblings object KeywordLookupObject @@ -54,7 +57,20 @@ object KeywordCompletion { val parserFilter = buildFilter(position) for (keywordToken in ALL_KEYWORDS) { - val keyword = keywordToken.getValue() + var keyword = keywordToken.getValue() + + if (keyword == COMPANION_KEYWORD.getValue()) { // complete "companion object" instead of simply "companion" unless we are before "object" already + fun PsiElement.isSpace() = this is PsiWhiteSpace && '\n' !in getText() + + var next = position.nextLeaf { !(it.isSpace() || it.getText() == "$") }?.getText() + if (next != null && next.startsWith("$")) { + next = next.substring(1) + } + if (next != OBJECT_KEYWORD.getValue()) { + keyword += " " + OBJECT_KEYWORD.getValue() + } + } + if (keyword.startsWith(prefix)/* use simple matching by prefix, not prefix matcher from completion*/ && parserFilter(keywordToken)) { val element = LookupElementBuilder.create(KeywordLookupObject, keyword) .bold() @@ -79,7 +95,7 @@ object KeywordCompletion { private class CommentFilter() : ElementFilter { override fun isAcceptable(element : Any?, context : PsiElement?) - = (element is PsiElement) && JetPsiUtil.isInComment(element as PsiElement) + = (element is PsiElement) && JetPsiUtil.isInComment(element) override fun isClassAcceptable(hintClass: Class) = true @@ -109,14 +125,14 @@ object KeywordCompletion { is JetWithExpressionInitializer -> { val initializer = _parent.getInitializer() if (prevParent == initializer) { - return buildFilterWithContext("val v = ", initializer, position) + return buildFilterWithContext("val v = ", initializer!!, position) } } is JetParameter -> { val default = _parent.getDefaultValue() if (prevParent == default) { - return buildFilterWithContext("val v = ", default, position) + return buildFilterWithContext("val v = ", default!!, position) } } } @@ -158,14 +174,14 @@ object KeywordCompletion { return { keywordTokenType -> val postfix = KEYWORD_TO_DUMMY_POSTFIX[keywordTokenType] ?: "" val file = psiFactory.createFile(prefixText + keywordTokenType.getValue() + postfix) - val elementAt = file.findElementAt(prefixText.length)!! + val elementAt = file.findElementAt(prefixText.length())!! when { !elementAt.getNode()!!.getElementType().matchesKeyword(keywordTokenType) -> false elementAt.getNonStrictParentOfType() != null -> false - elementAt.prevLeafSkipWhitespacesAndComments() is PsiErrorElement -> false + elementAt.prevLeaf { it !is PsiWhiteSpace && it !is PsiComment } is PsiErrorElement -> false else -> true } @@ -194,14 +210,14 @@ object KeywordCompletion { while (child != position) { if (child is JetDeclaration) { if (child == prevDeclaration) { - builder.appendReducedText(child!!) + builder.appendReducedText(child) } } else { builder.append(child!!.getText()) } - child = child!!.getNextSibling() + child = child.getNextSibling() } } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionCharFilter.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionCharFilter.kt index 2767fc2d7f5..c7741fc25cc 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionCharFilter.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionCharFilter.kt @@ -16,17 +16,19 @@ package org.jetbrains.kotlin.idea.completion -import com.intellij.codeInsight.lookup.CharFilter -import com.intellij.codeInsight.lookup.Lookup -import com.intellij.codeInsight.lookup.CharFilter.Result -import org.jetbrains.kotlin.psi.JetFile -import com.intellij.openapi.util.Key -import com.intellij.codeInsight.completion.CompletionService import com.intellij.codeInsight.completion.CompletionProgressIndicator +import com.intellij.codeInsight.completion.CompletionService +import com.intellij.codeInsight.lookup.CharFilter +import com.intellij.codeInsight.lookup.CharFilter.Result +import com.intellij.codeInsight.lookup.Lookup +import com.intellij.openapi.util.Key +import com.intellij.psi.PsiComment import com.intellij.psi.PsiElement -import org.jetbrains.kotlin.psi.psiUtil.prevLeafSkipWhitespacesAndComments +import com.intellij.psi.PsiWhiteSpace import org.jetbrains.kotlin.lexer.JetTokens +import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.JetFunctionLiteral +import org.jetbrains.kotlin.psi.psiUtil.prevLeaf public class KotlinCompletionCharFilter() : CharFilter() { companion object { @@ -86,9 +88,9 @@ public class KotlinCompletionCharFilter() : CharFilter() { } private fun isInFunctionLiteralStart(position: PsiElement): Boolean { - var prev = position.prevLeafSkipWhitespacesAndComments() + var prev = position.prevLeaf { it !is PsiWhiteSpace && it !is PsiComment } if (prev?.getNode()?.getElementType() == JetTokens.LPAR) { - prev = prev?.prevLeafSkipWhitespacesAndComments() + prev = prev?.prevLeaf { it !is PsiWhiteSpace && it !is PsiComment } } if (prev?.getNode()?.getElementType() != JetTokens.LBRACE) return false val functionLiteral = prev!!.getParent() as? JetFunctionLiteral ?: return false diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinKeywordInsertHandler.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinKeywordInsertHandler.kt index e10cdd160f0..18cf15db4cf 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinKeywordInsertHandler.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinKeywordInsertHandler.kt @@ -39,7 +39,7 @@ public object KotlinKeywordInsertHandler : InsertHandler { FILE_KEYWORD, CATCH_KEYWORD, FINALLY_KEYWORD, - DYNAMIC_KEYWORD).map { it.getValue() } + DYNAMIC_KEYWORD).map { it.getValue() } + "companion object" override fun handleInsert(context: InsertionContext, item: LookupElement) { val keyword = item.getLookupString() diff --git a/idea/idea-completion/testData/handlers/keywords/AddCompanionToObject.kt b/idea/idea-completion/testData/handlers/keywords/AddCompanionToObject.kt new file mode 100644 index 00000000000..6ed5f6a63f8 --- /dev/null +++ b/idea/idea-completion/testData/handlers/keywords/AddCompanionToObject.kt @@ -0,0 +1,7 @@ +class C { + compaobject { + + } +} + +// ELEMENT: "companion" diff --git a/idea/idea-completion/testData/handlers/keywords/AddCompanionToObject.kt.after b/idea/idea-completion/testData/handlers/keywords/AddCompanionToObject.kt.after new file mode 100644 index 00000000000..8ba27acda99 --- /dev/null +++ b/idea/idea-completion/testData/handlers/keywords/AddCompanionToObject.kt.after @@ -0,0 +1,7 @@ +class C { + companion object { + + } +} + +// ELEMENT: "companion" diff --git a/idea/idea-completion/testData/handlers/keywords/CompanionObject.kt b/idea/idea-completion/testData/handlers/keywords/CompanionObject.kt new file mode 100644 index 00000000000..a3b38d64ca4 --- /dev/null +++ b/idea/idea-completion/testData/handlers/keywords/CompanionObject.kt @@ -0,0 +1,3 @@ +class C { + compa +} \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/keywords/CompanionObject.kt.after b/idea/idea-completion/testData/handlers/keywords/CompanionObject.kt.after new file mode 100644 index 00000000000..bc65038821d --- /dev/null +++ b/idea/idea-completion/testData/handlers/keywords/CompanionObject.kt.after @@ -0,0 +1,3 @@ +class C { + companion object +} \ No newline at end of file diff --git a/idea/idea-completion/testData/keywords/AfterClassProperty.kt b/idea/idea-completion/testData/keywords/AfterClassProperty.kt index 297ccd66c98..a261c62433a 100644 --- a/idea/idea-completion/testData/keywords/AfterClassProperty.kt +++ b/idea/idea-completion/testData/keywords/AfterClassProperty.kt @@ -34,5 +34,5 @@ class MouseMovedEventArgs // EXIST: constructor // EXIST: init /*why?*/ -// EXIST: companion +// EXIST: companion object // NOTHING_ELSE: true diff --git a/idea/idea-completion/testData/keywords/AfterClasses.kt b/idea/idea-completion/testData/keywords/AfterClasses.kt index b1fd139701f..fc09b2f05b7 100644 --- a/idea/idea-completion/testData/keywords/AfterClasses.kt +++ b/idea/idea-completion/testData/keywords/AfterClasses.kt @@ -37,6 +37,6 @@ class B { // EXIST: var // EXIST: vararg /*why?*/ -// EXIST: companion +// EXIST: companion object /*TODO*/ // NOTHING_ELSE: true diff --git a/idea/idea-completion/testData/keywords/AfterFuns.kt b/idea/idea-completion/testData/keywords/AfterFuns.kt index 12c20e3ea11..a134ae264d2 100644 --- a/idea/idea-completion/testData/keywords/AfterFuns.kt +++ b/idea/idea-completion/testData/keywords/AfterFuns.kt @@ -37,5 +37,5 @@ class A { // EXIST: constructor // EXIST: init /*why?*/ -// EXIST: companion +// EXIST: companion object // NOTHING_ELSE: true diff --git a/idea/idea-completion/testData/keywords/CompanionObjectBeforeObject.kt b/idea/idea-completion/testData/keywords/CompanionObjectBeforeObject.kt new file mode 100644 index 00000000000..cb6fcdd8bac --- /dev/null +++ b/idea/idea-completion/testData/keywords/CompanionObjectBeforeObject.kt @@ -0,0 +1,6 @@ +class TestClass { + + object O {} +} + +// EXIST: "companion object" diff --git a/idea/idea-completion/testData/keywords/GlobalPropertyAccessors.kt b/idea/idea-completion/testData/keywords/GlobalPropertyAccessors.kt index 615a9bbc4ee..00b92953636 100644 --- a/idea/idea-completion/testData/keywords/GlobalPropertyAccessors.kt +++ b/idea/idea-completion/testData/keywords/GlobalPropertyAccessors.kt @@ -37,6 +37,6 @@ var a : Int // EXIST: var // EXIST: vararg /*why?*/ -// EXIST: companion +// EXIST: companion object /*TODO*/ // NOTHING_ELSE: true diff --git a/idea/idea-completion/testData/keywords/InClassBeforeFun.kt b/idea/idea-completion/testData/keywords/InClassBeforeFun.kt index 3e5b5629b34..a91ee655885 100644 --- a/idea/idea-completion/testData/keywords/InClassBeforeFun.kt +++ b/idea/idea-completion/testData/keywords/InClassBeforeFun.kt @@ -35,5 +35,5 @@ public class Test { // EXIST: constructor // EXIST: init /*why?*/ -// EXIST: companion +// EXIST: companion object // NOTHING_ELSE: true diff --git a/idea/idea-completion/testData/keywords/InClassScope.kt b/idea/idea-completion/testData/keywords/InClassScope.kt index b116eb373d8..5f0c00f5954 100644 --- a/idea/idea-completion/testData/keywords/InClassScope.kt +++ b/idea/idea-completion/testData/keywords/InClassScope.kt @@ -29,5 +29,5 @@ class TestClass { /*why?*/ // EXIST: constructor // EXIST: init -// EXIST: companion +// EXIST: companion object // NOTHING_ELSE: true diff --git a/idea/idea-completion/testData/keywords/InTopScopeAfterPackage.kt b/idea/idea-completion/testData/keywords/InTopScopeAfterPackage.kt index 83b926187d3..2b819dee2ba 100644 --- a/idea/idea-completion/testData/keywords/InTopScopeAfterPackage.kt +++ b/idea/idea-completion/testData/keywords/InTopScopeAfterPackage.kt @@ -28,6 +28,6 @@ package Test // EXIST: var // EXIST: vararg /*why?*/ -// EXIST: companion +// EXIST: companion object /*TODO*/ // NOTHING_ELSE: true diff --git a/idea/idea-completion/testData/keywords/PropertyAccessors.kt b/idea/idea-completion/testData/keywords/PropertyAccessors.kt index b5a802bb8f4..129ea6fb19d 100644 --- a/idea/idea-completion/testData/keywords/PropertyAccessors.kt +++ b/idea/idea-completion/testData/keywords/PropertyAccessors.kt @@ -33,5 +33,5 @@ class Some { /*why?*/ // EXIST: constructor // EXIST: init -// EXIST: companion +// EXIST: companion object // NOTHING_ELSE: true diff --git a/idea/idea-completion/testData/keywords/PropertyAccessors2.kt b/idea/idea-completion/testData/keywords/PropertyAccessors2.kt index 3705a84980f..3a580dac69d 100644 --- a/idea/idea-completion/testData/keywords/PropertyAccessors2.kt +++ b/idea/idea-completion/testData/keywords/PropertyAccessors2.kt @@ -33,5 +33,5 @@ class Some { /*why?*/ // EXIST: constructor // EXIST: init -// EXIST: companion +// EXIST: companion object // NOTHING_ELSE: true diff --git a/idea/idea-completion/testData/keywords/PropertySetter.kt b/idea/idea-completion/testData/keywords/PropertySetter.kt index b43162d7f16..684ec729285 100644 --- a/idea/idea-completion/testData/keywords/PropertySetter.kt +++ b/idea/idea-completion/testData/keywords/PropertySetter.kt @@ -35,5 +35,5 @@ class Some { /*why?*/ // EXIST: constructor // EXIST: init -// EXIST: companion +// EXIST: companion object // NOTHING_ELSE: true diff --git a/idea/idea-completion/testData/keywords/TopScope.kt b/idea/idea-completion/testData/keywords/TopScope.kt index de70cd067ba..56b9fa7c7b5 100644 --- a/idea/idea-completion/testData/keywords/TopScope.kt +++ b/idea/idea-completion/testData/keywords/TopScope.kt @@ -27,6 +27,6 @@ // EXIST: var // EXIST: vararg /*why?*/ -// EXIST: companion +// EXIST: companion object /*TODO*/ // NOTHING_ELSE: true diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/KeywordCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/KeywordCompletionTestGenerated.java index cdf9574319e..c1f54cebee5 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/KeywordCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/KeywordCompletionTestGenerated.java @@ -95,6 +95,12 @@ public class KeywordCompletionTestGenerated extends AbstractKeywordCompletionTes doTest(fileName); } + @TestMetadata("CompanionObjectBeforeObject.kt") + public void testCompanionObjectBeforeObject() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/keywords/CompanionObjectBeforeObject.kt"); + doTest(fileName); + } + @TestMetadata("ContinueWithLabel.kt") public void testContinueWithLabel() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/keywords/ContinueWithLabel.kt"); diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/KeywordCompletionHandlerTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/KeywordCompletionHandlerTestGenerated.java index c60efe13f2e..eb3d7049069 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/KeywordCompletionHandlerTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/KeywordCompletionHandlerTestGenerated.java @@ -31,6 +31,12 @@ import java.util.regex.Pattern; @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) public class KeywordCompletionHandlerTestGenerated extends AbstractKeywordCompletionHandlerTest { + @TestMetadata("AddCompanionToObject.kt") + public void testAddCompanionToObject() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/keywords/AddCompanionToObject.kt"); + doTest(fileName); + } + public void testAllFilesPresentInKeywords() throws Exception { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/handlers/keywords"), Pattern.compile("^(.+)\\.kt$"), true); } @@ -41,6 +47,12 @@ public class KeywordCompletionHandlerTestGenerated extends AbstractKeywordComple doTest(fileName); } + @TestMetadata("CompanionObject.kt") + public void testCompanionObject() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/keywords/CompanionObject.kt"); + doTest(fileName); + } + @TestMetadata("FileKeyword.kt") public void testFileKeyword() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/keywords/FileKeyword.kt");