From 9a969b0449d79417fcc2761339ab6e7cae88ad1e Mon Sep 17 00:00:00 2001 From: Simon Ogorodnik Date: Thu, 27 Apr 2017 15:59:30 +0300 Subject: [PATCH] Add basic completion for operator fun names #KT-11250 fixed --- .../expressions/OperatorConventions.java | 14 +++++ .../idea/completion/BasicCompletionSession.kt | 24 ++++++++ .../idea/completion/OperatorNameCompletion.kt | 58 +++++++++++++++++++ .../NoOperatorNameForTopLevel.kt | 5 ++ .../operatorNames/OperatorNameForExtension.kt | 36 ++++++++++++ .../operatorNames/OperatorNameForMember.kt | 34 +++++++++++ .../test/JSBasicCompletionTestGenerated.java | 27 +++++++++ .../test/JvmBasicCompletionTestGenerated.java | 27 +++++++++ 8 files changed, 225 insertions(+) create mode 100644 idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/OperatorNameCompletion.kt create mode 100644 idea/idea-completion/testData/basic/common/operatorNames/NoOperatorNameForTopLevel.kt create mode 100644 idea/idea-completion/testData/basic/common/operatorNames/OperatorNameForExtension.kt create mode 100644 idea/idea-completion/testData/basic/common/operatorNames/OperatorNameForMember.kt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/OperatorConventions.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/OperatorConventions.java index 92e85207a3e..687f6660444 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/OperatorConventions.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/OperatorConventions.java @@ -142,6 +142,20 @@ public class OperatorConventions { return null; } + @Nullable + public static KtToken getOperationSymbolForName(@NotNull Name name) { + if (!isConventionName(name)) return null; + + KtToken token; + token = BINARY_OPERATION_NAMES.inverse().get(name); + if (token != null) return token; + token = UNARY_OPERATION_NAMES.inverse().get(name); + if (token != null) return token; + token = ASSIGNMENT_OPERATIONS.inverse().get(name); + if (token != null) return token; + return null; + } + public static boolean isConventionName(@NotNull Name name) { return CONVENTION_NAMES.contains(name) || COMPONENT_REGEX.matches(name.asString()); } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt index f20d1d66db0..c8c526245a0 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt @@ -25,6 +25,7 @@ import com.intellij.codeInsight.template.TemplateManager import com.intellij.patterns.PatternCondition import com.intellij.patterns.StandardPatterns import com.intellij.psi.* +import com.intellij.psi.impl.source.tree.LeafPsiElement import com.intellij.psi.search.GlobalSearchScope import com.intellij.util.ProcessingContext import org.jetbrains.kotlin.descriptors.* @@ -96,6 +97,9 @@ class BasicCompletionSession( } } + if (OPERATOR_NAME.isApplicable()) + return OPERATOR_NAME + if (NamedArgumentCompletion.isOnlyNamedArgumentExpected(nameExpression)) { return NAMED_ARGUMENTS_ONLY } @@ -453,6 +457,26 @@ class BasicCompletionSession( } } + private val OPERATOR_NAME = object : CompletionKind { + override val descriptorKindFilter: DescriptorKindFilter? + get() = null + + fun isApplicable(): Boolean { + if (nameExpression == null || nameExpression != expression) return false + val func = position.getParentOfType(strict = false) ?: return false + val funcNameIdentifier = func.nameIdentifier ?: return false + val identifierInNameExpression = nameExpression.nextLeaf { it is LeafPsiElement && it.elementType == KtTokens.IDENTIFIER } ?: return false + if (!func.hasModifier(KtTokens.OPERATOR_KEYWORD) || identifierInNameExpression != funcNameIdentifier) return false + val originalFunc = toFromOriginalFileMapper.toOriginalFile(func) ?: return false + return !originalFunc.isTopLevel || (originalFunc.isExtensionDeclaration()) + } + + override fun doComplete() { + OperatorNameCompletion.doComplete(collector, descriptorNameFilter) + flushToResultSet() + } + } + private val DECLARATION_NAME = object : CompletionKind { override val descriptorKindFilter: DescriptorKindFilter? get() = null diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/OperatorNameCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/OperatorNameCompletion.kt new file mode 100644 index 00000000000..37ae5ec0cd5 --- /dev/null +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/OperatorNameCompletion.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.completion + +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupElementBuilder +import org.jetbrains.kotlin.lexer.KtSingleValueToken +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.types.expressions.OperatorConventions +import org.jetbrains.kotlin.util.OperatorNameConventions.COMPARE_TO +import org.jetbrains.kotlin.util.OperatorNameConventions.CONTAINS +import org.jetbrains.kotlin.util.OperatorNameConventions.EQUALS +import org.jetbrains.kotlin.util.OperatorNameConventions.GET +import org.jetbrains.kotlin.util.OperatorNameConventions.INVOKE +import org.jetbrains.kotlin.util.OperatorNameConventions.SET + +object OperatorNameCompletion { + + val additionalOperatorPresentation = mapOf( + SET to "[...] = ...", + GET to "[...]", + CONTAINS to "in !in", + COMPARE_TO to "< > <= >=", + EQUALS to "== !=", + INVOKE to "(...)" + ) + + private fun buildLookupElement(opName: Name): LookupElement { + val element = LookupElementBuilder.create(opName) + + val symbol = + (OperatorConventions.getOperationSymbolForName(opName) as? KtSingleValueToken)?.value ?: + additionalOperatorPresentation[opName] + + if (symbol != null) return element.withTypeText(symbol) + return element + } + + fun doComplete(collector: LookupElementsCollector, descriptorNameFilter: (String) -> Boolean) { + collector.addElements(OperatorConventions.CONVENTION_NAMES + .filter { descriptorNameFilter(it.asString()) } + .map(this::buildLookupElement)) + } +} \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/operatorNames/NoOperatorNameForTopLevel.kt b/idea/idea-completion/testData/basic/common/operatorNames/NoOperatorNameForTopLevel.kt new file mode 100644 index 00000000000..e40b31a1231 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/operatorNames/NoOperatorNameForTopLevel.kt @@ -0,0 +1,5 @@ +operator fun () { + +} + +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/operatorNames/OperatorNameForExtension.kt b/idea/idea-completion/testData/basic/common/operatorNames/OperatorNameForExtension.kt new file mode 100644 index 00000000000..95d49eef109 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/operatorNames/OperatorNameForExtension.kt @@ -0,0 +1,36 @@ +class Some { + +} + +operator fun Some.() { + +} + +// EXIST: {"lookupString":"compareTo","typeText":"< > <= >="} +// EXIST: {"lookupString":"contains","typeText":"in !in"} +// EXIST: {"lookupString":"dec","typeText":"--"} +// EXIST: {"lookupString":"div","typeText":"/"} +// EXIST: {"lookupString":"divAssign","typeText":"/="} +// EXIST: {"lookupString":"equals","typeText":"== !="} +// EXIST: {"lookupString":"get","typeText":"[...]"} +// EXIST: {"lookupString":"getValue"} +// EXIST: {"lookupString":"hasNext"} +// EXIST: {"lookupString":"inc","typeText":"++"} +// EXIST: {"lookupString":"invoke","typeText":"(...)"} +// EXIST: {"lookupString":"iterator"} +// EXIST: {"lookupString":"minus","typeText":"-"} +// EXIST: {"lookupString":"minusAssign","typeText":"-="} +// EXIST: {"lookupString":"next"} +// EXIST: {"lookupString":"not","typeText":"!"} +// EXIST: {"lookupString":"plus","typeText":"+"} +// EXIST: {"lookupString":"plusAssign","typeText":"+="} +// EXIST: {"lookupString":"rangeTo","typeText":".."} +// EXIST: {"lookupString":"rem","typeText":"%"} +// EXIST: {"lookupString":"remAssign","typeText":"%="} +// EXIST: {"lookupString":"set","typeText":"[...] = ..."} +// EXIST: {"lookupString":"setValue"} +// EXIST: {"lookupString":"times","typeText":"*"} +// EXIST: {"lookupString":"timesAssign","typeText":"*="} +// EXIST: {"lookupString":"unaryMinus","typeText":"-"} +// EXIST: {"lookupString":"unaryPlus","typeText":"+"} +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/operatorNames/OperatorNameForMember.kt b/idea/idea-completion/testData/basic/common/operatorNames/OperatorNameForMember.kt new file mode 100644 index 00000000000..c6dc0ca653a --- /dev/null +++ b/idea/idea-completion/testData/basic/common/operatorNames/OperatorNameForMember.kt @@ -0,0 +1,34 @@ +class Some { + operator fun () { + + } +} + +// EXIST: {"lookupString":"compareTo","typeText":"< > <= >="} +// EXIST: {"lookupString":"contains","typeText":"in !in"} +// EXIST: {"lookupString":"dec","typeText":"--"} +// EXIST: {"lookupString":"div","typeText":"/"} +// EXIST: {"lookupString":"divAssign","typeText":"/="} +// EXIST: {"lookupString":"equals","typeText":"== !="} +// EXIST: {"lookupString":"get","typeText":"[...]"} +// EXIST: {"lookupString":"getValue"} +// EXIST: {"lookupString":"hasNext"} +// EXIST: {"lookupString":"inc","typeText":"++"} +// EXIST: {"lookupString":"invoke","typeText":"(...)"} +// EXIST: {"lookupString":"iterator"} +// EXIST: {"lookupString":"minus","typeText":"-"} +// EXIST: {"lookupString":"minusAssign","typeText":"-="} +// EXIST: {"lookupString":"next"} +// EXIST: {"lookupString":"not","typeText":"!"} +// EXIST: {"lookupString":"plus","typeText":"+"} +// EXIST: {"lookupString":"plusAssign","typeText":"+="} +// EXIST: {"lookupString":"rangeTo","typeText":".."} +// EXIST: {"lookupString":"rem","typeText":"%"} +// EXIST: {"lookupString":"remAssign","typeText":"%="} +// EXIST: {"lookupString":"set","typeText":"[...] = ..."} +// EXIST: {"lookupString":"setValue"} +// EXIST: {"lookupString":"times","typeText":"*"} +// EXIST: {"lookupString":"timesAssign","typeText":"*="} +// EXIST: {"lookupString":"unaryMinus","typeText":"-"} +// EXIST: {"lookupString":"unaryPlus","typeText":"+"} +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java index 22928477e17..695a4dd20f1 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java @@ -2021,6 +2021,33 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes } } + @TestMetadata("idea/idea-completion/testData/basic/common/operatorNames") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class OperatorNames extends AbstractJSBasicCompletionTest { + public void testAllFilesPresentInOperatorNames() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/operatorNames"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("NoOperatorNameForTopLevel.kt") + public void testNoOperatorNameForTopLevel() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/operatorNames/NoOperatorNameForTopLevel.kt"); + doTest(fileName); + } + + @TestMetadata("OperatorNameForExtension.kt") + public void testOperatorNameForExtension() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/operatorNames/OperatorNameForExtension.kt"); + doTest(fileName); + } + + @TestMetadata("OperatorNameForMember.kt") + public void testOperatorNameForMember() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/operatorNames/OperatorNameForMember.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/idea-completion/testData/basic/common/override") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java index 51c3f27d2a7..fd40e36720a 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java @@ -2021,6 +2021,33 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT } } + @TestMetadata("idea/idea-completion/testData/basic/common/operatorNames") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class OperatorNames extends AbstractJvmBasicCompletionTest { + public void testAllFilesPresentInOperatorNames() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/operatorNames"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("NoOperatorNameForTopLevel.kt") + public void testNoOperatorNameForTopLevel() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/operatorNames/NoOperatorNameForTopLevel.kt"); + doTest(fileName); + } + + @TestMetadata("OperatorNameForExtension.kt") + public void testOperatorNameForExtension() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/operatorNames/OperatorNameForExtension.kt"); + doTest(fileName); + } + + @TestMetadata("OperatorNameForMember.kt") + public void testOperatorNameForMember() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/operatorNames/OperatorNameForMember.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/idea-completion/testData/basic/common/override") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)