KT-7349 Replace keyword "companion" in completion with "companion object"

This commit is contained in:
Valentin Kipyatkov
2015-05-14 18:15:52 +03:00
parent db40f1ee5a
commit 8ea0441348
22 changed files with 109 additions and 64 deletions
@@ -16,35 +16,26 @@
package org.jetbrains.kotlin.psi.psiUtil 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 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.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.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.stubs.StubElement
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.JetNodeTypes 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.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 org.jetbrains.kotlin.utils.addToStdlib.lastIsInstanceOrNull
import java.util.ArrayList
import java.util.Collections
import kotlin.test.assertTrue
public fun JetCallElement.getCallNameExpression(): JetSimpleNameExpression? { public fun JetCallElement.getCallNameExpression(): JetSimpleNameExpression? {
val calleeExpression = getCalleeExpression() ?: return null 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? public fun PsiElement.nextLeaf(skipEmptyElements: Boolean = false): PsiElement?
= PsiTreeUtil.nextLeaf(this, skipEmptyElements) = PsiTreeUtil.nextLeaf(this, skipEmptyElements)
public fun PsiElement.prevLeafSkipWhitespacesAndComments(): PsiElement? { public fun PsiElement.prevLeaf(filter: (PsiElement) -> Boolean): PsiElement? {
var leaf = prevLeaf() var leaf = prevLeaf()
while (leaf is PsiWhiteSpace || leaf is PsiComment) { while (leaf != null && !filter(leaf)) {
leaf = leaf.prevLeaf() leaf = leaf.prevLeaf()
} }
return leaf return leaf
} }
public fun PsiElement.prevLeafSkipWhitespaces(): PsiElement? { public fun PsiElement.nextLeaf(filter: (PsiElement) -> Boolean): PsiElement? {
var leaf = prevLeaf()
while (leaf is PsiWhiteSpace) {
leaf = leaf.prevLeaf()
}
return leaf
}
public fun PsiElement.nextLeafSkipWhitespacesAndComments(): PsiElement? {
var leaf = nextLeaf() var leaf = nextLeaf()
while (leaf is PsiWhiteSpace || leaf is PsiComment) { while (leaf != null && !filter(leaf)) {
leaf = leaf.nextLeaf() leaf = leaf.nextLeaf()
} }
return leaf return leaf
@@ -19,8 +19,10 @@ package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiErrorElement import com.intellij.psi.PsiErrorElement
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.filters.* import com.intellij.psi.filters.*
import com.intellij.psi.filters.position.LeftNeighbour import com.intellij.psi.filters.position.LeftNeighbour
import com.intellij.psi.filters.position.PositionElementFilter 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.lexer.JetTokens.*
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType 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 import org.jetbrains.kotlin.psi.psiUtil.siblings
object KeywordLookupObject object KeywordLookupObject
@@ -54,7 +57,20 @@ object KeywordCompletion {
val parserFilter = buildFilter(position) val parserFilter = buildFilter(position)
for (keywordToken in ALL_KEYWORDS) { 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)) { if (keyword.startsWith(prefix)/* use simple matching by prefix, not prefix matcher from completion*/ && parserFilter(keywordToken)) {
val element = LookupElementBuilder.create(KeywordLookupObject, keyword) val element = LookupElementBuilder.create(KeywordLookupObject, keyword)
.bold() .bold()
@@ -79,7 +95,7 @@ object KeywordCompletion {
private class CommentFilter() : ElementFilter { private class CommentFilter() : ElementFilter {
override fun isAcceptable(element : Any?, context : PsiElement?) 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<out Any?>) override fun isClassAcceptable(hintClass: Class<out Any?>)
= true = true
@@ -109,14 +125,14 @@ object KeywordCompletion {
is JetWithExpressionInitializer -> { is JetWithExpressionInitializer -> {
val initializer = _parent.getInitializer() val initializer = _parent.getInitializer()
if (prevParent == initializer) { if (prevParent == initializer) {
return buildFilterWithContext("val v = ", initializer, position) return buildFilterWithContext("val v = ", initializer!!, position)
} }
} }
is JetParameter -> { is JetParameter -> {
val default = _parent.getDefaultValue() val default = _parent.getDefaultValue()
if (prevParent == default) { if (prevParent == default) {
return buildFilterWithContext("val v = ", default, position) return buildFilterWithContext("val v = ", default!!, position)
} }
} }
} }
@@ -158,14 +174,14 @@ object KeywordCompletion {
return { keywordTokenType -> return { keywordTokenType ->
val postfix = KEYWORD_TO_DUMMY_POSTFIX[keywordTokenType] ?: "" val postfix = KEYWORD_TO_DUMMY_POSTFIX[keywordTokenType] ?: ""
val file = psiFactory.createFile(prefixText + keywordTokenType.getValue() + postfix) val file = psiFactory.createFile(prefixText + keywordTokenType.getValue() + postfix)
val elementAt = file.findElementAt(prefixText.length)!! val elementAt = file.findElementAt(prefixText.length())!!
when { when {
!elementAt.getNode()!!.getElementType().matchesKeyword(keywordTokenType) -> false !elementAt.getNode()!!.getElementType().matchesKeyword(keywordTokenType) -> false
elementAt.getNonStrictParentOfType<PsiErrorElement>() != null -> false elementAt.getNonStrictParentOfType<PsiErrorElement>() != null -> false
elementAt.prevLeafSkipWhitespacesAndComments() is PsiErrorElement -> false elementAt.prevLeaf { it !is PsiWhiteSpace && it !is PsiComment } is PsiErrorElement -> false
else -> true else -> true
} }
@@ -194,14 +210,14 @@ object KeywordCompletion {
while (child != position) { while (child != position) {
if (child is JetDeclaration) { if (child is JetDeclaration) {
if (child == prevDeclaration) { if (child == prevDeclaration) {
builder.appendReducedText(child!!) builder.appendReducedText(child)
} }
} }
else { else {
builder.append(child!!.getText()) builder.append(child!!.getText())
} }
child = child!!.getNextSibling() child = child.getNextSibling()
} }
} }
@@ -16,17 +16,19 @@
package org.jetbrains.kotlin.idea.completion 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.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 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.lexer.JetTokens
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetFunctionLiteral import org.jetbrains.kotlin.psi.JetFunctionLiteral
import org.jetbrains.kotlin.psi.psiUtil.prevLeaf
public class KotlinCompletionCharFilter() : CharFilter() { public class KotlinCompletionCharFilter() : CharFilter() {
companion object { companion object {
@@ -86,9 +88,9 @@ public class KotlinCompletionCharFilter() : CharFilter() {
} }
private fun isInFunctionLiteralStart(position: PsiElement): Boolean { 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) { 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 if (prev?.getNode()?.getElementType() != JetTokens.LBRACE) return false
val functionLiteral = prev!!.getParent() as? JetFunctionLiteral ?: return false val functionLiteral = prev!!.getParent() as? JetFunctionLiteral ?: return false
@@ -39,7 +39,7 @@ public object KotlinKeywordInsertHandler : InsertHandler<LookupElement> {
FILE_KEYWORD, FILE_KEYWORD,
CATCH_KEYWORD, CATCH_KEYWORD,
FINALLY_KEYWORD, FINALLY_KEYWORD,
DYNAMIC_KEYWORD).map { it.getValue() } DYNAMIC_KEYWORD).map { it.getValue() } + "companion object"
override fun handleInsert(context: InsertionContext, item: LookupElement) { override fun handleInsert(context: InsertionContext, item: LookupElement) {
val keyword = item.getLookupString() val keyword = item.getLookupString()
@@ -0,0 +1,7 @@
class C {
compa<caret>object {
}
}
// ELEMENT: "companion"
@@ -0,0 +1,7 @@
class C {
companion <caret>object {
}
}
// ELEMENT: "companion"
@@ -0,0 +1,3 @@
class C {
compa<caret>
}
@@ -0,0 +1,3 @@
class C {
companion object<caret>
}
@@ -34,5 +34,5 @@ class MouseMovedEventArgs
// EXIST: constructor // EXIST: constructor
// EXIST: init // EXIST: init
/*why?*/ /*why?*/
// EXIST: companion // EXIST: companion object
// NOTHING_ELSE: true // NOTHING_ELSE: true
@@ -37,6 +37,6 @@ class B {
// EXIST: var // EXIST: var
// EXIST: vararg // EXIST: vararg
/*why?*/ /*why?*/
// EXIST: companion // EXIST: companion object
/*TODO*/ /*TODO*/
// NOTHING_ELSE: true // NOTHING_ELSE: true
@@ -37,5 +37,5 @@ class A {
// EXIST: constructor // EXIST: constructor
// EXIST: init // EXIST: init
/*why?*/ /*why?*/
// EXIST: companion // EXIST: companion object
// NOTHING_ELSE: true // NOTHING_ELSE: true
@@ -0,0 +1,6 @@
class TestClass {
<caret>
object O {}
}
// EXIST: "companion object"
@@ -37,6 +37,6 @@ var a : Int
// EXIST: var // EXIST: var
// EXIST: vararg // EXIST: vararg
/*why?*/ /*why?*/
// EXIST: companion // EXIST: companion object
/*TODO*/ /*TODO*/
// NOTHING_ELSE: true // NOTHING_ELSE: true
@@ -35,5 +35,5 @@ public class Test {
// EXIST: constructor // EXIST: constructor
// EXIST: init // EXIST: init
/*why?*/ /*why?*/
// EXIST: companion // EXIST: companion object
// NOTHING_ELSE: true // NOTHING_ELSE: true
@@ -29,5 +29,5 @@ class TestClass {
/*why?*/ /*why?*/
// EXIST: constructor // EXIST: constructor
// EXIST: init // EXIST: init
// EXIST: companion // EXIST: companion object
// NOTHING_ELSE: true // NOTHING_ELSE: true
@@ -28,6 +28,6 @@ package Test
// EXIST: var // EXIST: var
// EXIST: vararg // EXIST: vararg
/*why?*/ /*why?*/
// EXIST: companion // EXIST: companion object
/*TODO*/ /*TODO*/
// NOTHING_ELSE: true // NOTHING_ELSE: true
@@ -33,5 +33,5 @@ class Some {
/*why?*/ /*why?*/
// EXIST: constructor // EXIST: constructor
// EXIST: init // EXIST: init
// EXIST: companion // EXIST: companion object
// NOTHING_ELSE: true // NOTHING_ELSE: true
@@ -33,5 +33,5 @@ class Some {
/*why?*/ /*why?*/
// EXIST: constructor // EXIST: constructor
// EXIST: init // EXIST: init
// EXIST: companion // EXIST: companion object
// NOTHING_ELSE: true // NOTHING_ELSE: true
@@ -35,5 +35,5 @@ class Some {
/*why?*/ /*why?*/
// EXIST: constructor // EXIST: constructor
// EXIST: init // EXIST: init
// EXIST: companion // EXIST: companion object
// NOTHING_ELSE: true // NOTHING_ELSE: true
@@ -27,6 +27,6 @@
// EXIST: var // EXIST: var
// EXIST: vararg // EXIST: vararg
/*why?*/ /*why?*/
// EXIST: companion // EXIST: companion object
/*TODO*/ /*TODO*/
// NOTHING_ELSE: true // NOTHING_ELSE: true
@@ -95,6 +95,12 @@ public class KeywordCompletionTestGenerated extends AbstractKeywordCompletionTes
doTest(fileName); 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") @TestMetadata("ContinueWithLabel.kt")
public void testContinueWithLabel() throws Exception { public void testContinueWithLabel() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/keywords/ContinueWithLabel.kt"); String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/keywords/ContinueWithLabel.kt");
@@ -31,6 +31,12 @@ import java.util.regex.Pattern;
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
public class KeywordCompletionHandlerTestGenerated extends AbstractKeywordCompletionHandlerTest { 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 { public void testAllFilesPresentInKeywords() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/handlers/keywords"), Pattern.compile("^(.+)\\.kt$"), true); 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); 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") @TestMetadata("FileKeyword.kt")
public void testFileKeyword() throws Exception { public void testFileKeyword() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/keywords/FileKeyword.kt"); String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/keywords/FileKeyword.kt");