diff --git a/idea/idea-completion/idea-completion.iml b/idea/idea-completion/idea-completion.iml index 8b06fd2ca4f..ade1db9de2e 100644 --- a/idea/idea-completion/idea-completion.iml +++ b/idea/idea-completion/idea-completion.iml @@ -23,5 +23,6 @@ + \ No newline at end of file diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt index 268fd3d21ef..274dc6e51ac 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt @@ -79,6 +79,8 @@ public class KotlinCompletionContributor : CompletionContributor() { isInUnclosedSuperQualifier(tokenBefore) -> CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + ">" + isInSimpleStringTemplate(tokenBefore) -> CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + else -> specialLambdaSignatureDummyIdentifier(tokenBefore) ?: specialExtensionReceiverDummyIdentifier(tokenBefore) ?: specialInTypeArgsDummyIdentifier(tokenBefore) @@ -242,6 +244,8 @@ public class KotlinCompletionContributor : CompletionContributor() { return } + if (PropertyKeyCompletion.perform(parameters, result)) return + try { result.restartCompletionWhenNothingMatches() @@ -432,4 +436,8 @@ public class KotlinCompletionContributor : CompletionContributor() { val superToken = ltToken.prevLeaf { it !is PsiWhiteSpace && it !is PsiComment } return superToken?.node?.elementType == JetTokens.SUPER_KEYWORD } + + private fun isInSimpleStringTemplate(tokenBefore: PsiElement?): Boolean { + return tokenBefore?.parents?.firstIsInstanceOrNull()?.isPlain() ?: false + } } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/PropertyKeyCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/PropertyKeyCompletion.kt new file mode 100644 index 00000000000..13786446cca --- /dev/null +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/PropertyKeyCompletion.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2010-2015 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.completion.CompletionParameters +import com.intellij.codeInsight.completion.CompletionResultSet +import com.intellij.lang.properties.references.PropertiesCompletionContributor +import com.intellij.lang.properties.references.PropertyReference +import org.jetbrains.kotlin.psi.JetStringTemplateExpression +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.isPlain +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull + +object PropertyKeyCompletion { + fun perform(parameters: CompletionParameters, result: CompletionResultSet): Boolean { + val template = parameters.position.getStrictParentOfType() ?: return false + if (!template.isPlain()) return false + + val references = template.references + val propertyReference = references.firstIsInstanceOrNull() ?: return false + if (PropertiesCompletionContributor.hasMoreImportantReference(references, propertyReference)) return false + + val startOffset = parameters.offset + val offsetInElement = startOffset - template.startOffset + val range = propertyReference.rangeInElement + if (offsetInElement < range.startOffset) return false + + val prefix = template.text.substring(range.startOffset, offsetInElement) + val variants = PropertiesCompletionContributor.getVariants(propertyReference) + result.withPrefixMatcher(prefix).addAllElements(variants.toList()) + + return true + } +} diff --git a/idea/idea-completion/testData/basic/multifile/PropertyKeysEmptyString/PropertyKeysEmptyString.dependency.kt b/idea/idea-completion/testData/basic/multifile/PropertyKeysEmptyString/PropertyKeysEmptyString.dependency.kt new file mode 100644 index 00000000000..1e1932b2d0f --- /dev/null +++ b/idea/idea-completion/testData/basic/multifile/PropertyKeysEmptyString/PropertyKeysEmptyString.dependency.kt @@ -0,0 +1,5 @@ +package org.jetbrains.annotations + +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.LOCAL_VARIABLE, AnnotationTarget.FIELD) +public annotation class PropertyKey(public val resourceBundle: String) \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/multifile/PropertyKeysEmptyString/PropertyKeysEmptyString.kt b/idea/idea-completion/testData/basic/multifile/PropertyKeysEmptyString/PropertyKeysEmptyString.kt new file mode 100644 index 00000000000..40cb12de4ac --- /dev/null +++ b/idea/idea-completion/testData/basic/multifile/PropertyKeysEmptyString/PropertyKeysEmptyString.kt @@ -0,0 +1,13 @@ +import org.jetbrains.annotations.PropertyKey + +fun message(@PropertyKey(resourceBundle = "PropertyKeysEmptyString") key: String) = key + +fun test() { + message("") +} + +// EXIST: { lookupString: "foo.bar", itemText: "foo.bar", tailText: "1", typeText: "PropertyKeysEmptyString" } +// EXIST: { lookupString: "bar.baz", itemText: "bar.baz", tailText: "2", typeText: "PropertyKeysEmptyString" } +// EXIST: { lookupString: "foo.bar.baz", itemText: "foo.bar.baz", tailText: "3", typeText: "PropertyKeysEmptyString" } +// EXIST: { lookupString: "foo.test", itemText: "foo.test", tailText: "4", typeText: "PropertyKeysEmptyString" } +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/multifile/PropertyKeysEmptyString/PropertyKeysEmptyString.properties b/idea/idea-completion/testData/basic/multifile/PropertyKeysEmptyString/PropertyKeysEmptyString.properties new file mode 100644 index 00000000000..5999617526a --- /dev/null +++ b/idea/idea-completion/testData/basic/multifile/PropertyKeysEmptyString/PropertyKeysEmptyString.properties @@ -0,0 +1,4 @@ +foo.bar = 1 +bar.baz = 2 +foo.bar.baz = 3 +foo.test = 4 \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/multifile/PropertyKeysNoPrefix/PropertyKeysNoPrefix.dependency.kt b/idea/idea-completion/testData/basic/multifile/PropertyKeysNoPrefix/PropertyKeysNoPrefix.dependency.kt new file mode 100644 index 00000000000..1e1932b2d0f --- /dev/null +++ b/idea/idea-completion/testData/basic/multifile/PropertyKeysNoPrefix/PropertyKeysNoPrefix.dependency.kt @@ -0,0 +1,5 @@ +package org.jetbrains.annotations + +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.LOCAL_VARIABLE, AnnotationTarget.FIELD) +public annotation class PropertyKey(public val resourceBundle: String) \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/multifile/PropertyKeysNoPrefix/PropertyKeysNoPrefix.kt b/idea/idea-completion/testData/basic/multifile/PropertyKeysNoPrefix/PropertyKeysNoPrefix.kt new file mode 100644 index 00000000000..e3412202680 --- /dev/null +++ b/idea/idea-completion/testData/basic/multifile/PropertyKeysNoPrefix/PropertyKeysNoPrefix.kt @@ -0,0 +1,13 @@ +import org.jetbrains.annotations.PropertyKey + +fun message(@PropertyKey(resourceBundle = "PropertyKeysNoPrefix") key: String) = key + +fun test() { + message("foo") +} + +// EXIST: { lookupString: "foo.bar", itemText: "foo.bar", tailText: "1", typeText: "PropertyKeysNoPrefix" } +// EXIST: { lookupString: "bar.baz", itemText: "bar.baz", tailText: "2", typeText: "PropertyKeysNoPrefix" } +// EXIST: { lookupString: "foo.bar.baz", itemText: "foo.bar.baz", tailText: "3", typeText: "PropertyKeysNoPrefix" } +// EXIST: { lookupString: "foo.test", itemText: "foo.test", tailText: "4", typeText: "PropertyKeysNoPrefix" } +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/multifile/PropertyKeysNoPrefix/PropertyKeysNoPrefix.properties b/idea/idea-completion/testData/basic/multifile/PropertyKeysNoPrefix/PropertyKeysNoPrefix.properties new file mode 100644 index 00000000000..5999617526a --- /dev/null +++ b/idea/idea-completion/testData/basic/multifile/PropertyKeysNoPrefix/PropertyKeysNoPrefix.properties @@ -0,0 +1,4 @@ +foo.bar = 1 +bar.baz = 2 +foo.bar.baz = 3 +foo.test = 4 \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/multifile/PropertyKeysWithPrefix/PropertyKeysWithPrefix.dependency.kt b/idea/idea-completion/testData/basic/multifile/PropertyKeysWithPrefix/PropertyKeysWithPrefix.dependency.kt new file mode 100644 index 00000000000..1e1932b2d0f --- /dev/null +++ b/idea/idea-completion/testData/basic/multifile/PropertyKeysWithPrefix/PropertyKeysWithPrefix.dependency.kt @@ -0,0 +1,5 @@ +package org.jetbrains.annotations + +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.LOCAL_VARIABLE, AnnotationTarget.FIELD) +public annotation class PropertyKey(public val resourceBundle: String) \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/multifile/PropertyKeysWithPrefix/PropertyKeysWithPrefix.kt b/idea/idea-completion/testData/basic/multifile/PropertyKeysWithPrefix/PropertyKeysWithPrefix.kt new file mode 100644 index 00000000000..c6459771214 --- /dev/null +++ b/idea/idea-completion/testData/basic/multifile/PropertyKeysWithPrefix/PropertyKeysWithPrefix.kt @@ -0,0 +1,12 @@ +import org.jetbrains.annotations.PropertyKey + +fun message(@PropertyKey(resourceBundle = "PropertyKeysWithPrefix") key: String) = key + +fun test() { + message("foo.") +} + +// EXIST: { lookupString: "foo.bar", itemText: "foo.bar", tailText: "1", typeText: "PropertyKeysWithPrefix" } +// EXIST: { lookupString: "foo.bar.baz", itemText: "foo.bar.baz", tailText: "3", typeText: "PropertyKeysWithPrefix" } +// EXIST: { lookupString: "foo.test", itemText: "foo.test", tailText: "4", typeText: "PropertyKeysWithPrefix" } +// NOTHING_ELSE \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/multifile/PropertyKeysWithPrefix/PropertyKeysWithPrefix.properties b/idea/idea-completion/testData/basic/multifile/PropertyKeysWithPrefix/PropertyKeysWithPrefix.properties new file mode 100644 index 00000000000..5999617526a --- /dev/null +++ b/idea/idea-completion/testData/basic/multifile/PropertyKeysWithPrefix/PropertyKeysWithPrefix.properties @@ -0,0 +1,4 @@ +foo.bar = 1 +bar.baz = 2 +foo.bar.baz = 3 +foo.test = 4 \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixEnter-1.kt b/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixEnter-1.kt new file mode 100644 index 00000000000..b0e268783e4 --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixEnter-1.kt @@ -0,0 +1,7 @@ +import org.jetbrains.annotations.PropertyKey + +fun message(@PropertyKey(resourceBundle = "TestBundle") key: String) = key + +fun test() { + message("foo.b") +} \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixEnter-2.kt b/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixEnter-2.kt new file mode 100644 index 00000000000..1e1932b2d0f --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixEnter-2.kt @@ -0,0 +1,5 @@ +package org.jetbrains.annotations + +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.LOCAL_VARIABLE, AnnotationTarget.FIELD) +public annotation class PropertyKey(public val resourceBundle: String) \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixEnter.kt.after b/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixEnter.kt.after new file mode 100644 index 00000000000..d2bd1b98c64 --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixEnter.kt.after @@ -0,0 +1,7 @@ +import org.jetbrains.annotations.PropertyKey + +fun message(@PropertyKey(resourceBundle = "TestBundle") key: String) = key + +fun test() { + message("foo.barb") +} \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixTab-1.kt b/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixTab-1.kt new file mode 100644 index 00000000000..b0e268783e4 --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixTab-1.kt @@ -0,0 +1,7 @@ +import org.jetbrains.annotations.PropertyKey + +fun message(@PropertyKey(resourceBundle = "TestBundle") key: String) = key + +fun test() { + message("foo.b") +} \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixTab-2.kt b/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixTab-2.kt new file mode 100644 index 00000000000..1e1932b2d0f --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixTab-2.kt @@ -0,0 +1,5 @@ +package org.jetbrains.annotations + +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.LOCAL_VARIABLE, AnnotationTarget.FIELD) +public annotation class PropertyKey(public val resourceBundle: String) \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixTab.kt.after b/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixTab.kt.after new file mode 100644 index 00000000000..98edf6d91e9 --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/PropertyKeysWithPrefixTab.kt.after @@ -0,0 +1,7 @@ +import org.jetbrains.annotations.PropertyKey + +fun message(@PropertyKey(resourceBundle = "TestBundle") key: String) = key + +fun test() { + message("foo.bar") +} \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/TestBundle.properties b/idea/idea-completion/testData/handlers/multifile/TestBundle.properties new file mode 100644 index 00000000000..f3e552c26a7 --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/TestBundle.properties @@ -0,0 +1,2 @@ +foo.bar = 1 +bar.baz = 2 \ No newline at end of file diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/MultiFileJvmBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/MultiFileJvmBasicCompletionTestGenerated.java index abf17bb8a8b..5a3ff4c4b33 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/MultiFileJvmBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/MultiFileJvmBasicCompletionTestGenerated.java @@ -293,6 +293,24 @@ public class MultiFileJvmBasicCompletionTestGenerated extends AbstractMultiFileJ doTest(fileName); } + @TestMetadata("PropertyKeysEmptyString") + public void testPropertyKeysEmptyString() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/multifile/PropertyKeysEmptyString/"); + doTest(fileName); + } + + @TestMetadata("PropertyKeysNoPrefix") + public void testPropertyKeysNoPrefix() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/multifile/PropertyKeysNoPrefix/"); + doTest(fileName); + } + + @TestMetadata("PropertyKeysWithPrefix") + public void testPropertyKeysWithPrefix() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/multifile/PropertyKeysWithPrefix/"); + doTest(fileName); + } + @TestMetadata("SyntheticExtensionForGenericClass") public void testSyntheticExtensionForGenericClass() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/multifile/SyntheticExtensionForGenericClass/"); diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/CompletionMultifileHandlerTest.kt b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/CompletionMultifileHandlerTest.kt index 68c5546ba0b..dffa89898af 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/CompletionMultifileHandlerTest.kt +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/CompletionMultifileHandlerTest.kt @@ -77,9 +77,18 @@ class CompletionMultiFileHandlerTest : KotlinCompletionTestCase() { doTest('!') } - fun doTest(completionChar: Char = '\n') { + fun testPropertyKeysWithPrefixEnter() { + doTest('\n', "TestBundle.properties") + } + + fun testPropertyKeysWithPrefixTab() { + doTest('\t', "TestBundle.properties") + } + + fun doTest(completionChar: Char = '\n', vararg extraFileNames: String) { val fileName = getTestName(false) + configureByFiles(null, *extraFileNames) configureByFiles(null, fileName + "-1.kt", fileName + "-2.kt") complete(2) if (myItems != null) {