From 8946e5f3536c38e298372982b6d6825e3eff1955 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Mon, 9 Nov 2015 18:45:41 +0300 Subject: [PATCH] Code completion for string templates after simple name + dot --- .../idea/completion/BasicCompletionSession.kt | 1 + .../idea/completion/CompletionSession.kt | 8 +- .../completion/KotlinCompletionContributor.kt | 119 +++++++++++++----- .../smart/SmartCompletionSession.kt | 8 +- .../{ => inStringLiterals}/StringTemplate1.kt | 2 +- .../{ => inStringLiterals}/StringTemplate2.kt | 0 .../inStringLiterals/StringTemplateDot.kt | 7 ++ .../StringTemplateDotSomething.kt | 7 ++ .../basic/stringTemplate/AfterDot1.kt | 6 + .../basic/stringTemplate/AfterDot1.kt.after | 6 + .../basic/stringTemplate/AfterDot2.kt | 5 + .../basic/stringTemplate/AfterDot2.kt.after | 5 + .../basic/stringTemplate/AfterDot3.kt | 6 + .../basic/stringTemplate/AfterDot3.kt.after | 6 + .../test/JSBasicCompletionTestGenerated.java | 45 +++++-- .../test/JvmBasicCompletionTestGenerated.java | 45 +++++-- .../BasicCompletionHandlerTestGenerated.java | 18 +++ .../idea/editor/KotlinTypedHandler.java | 16 ++- 18 files changed, 248 insertions(+), 62 deletions(-) rename idea/idea-completion/testData/basic/common/{ => inStringLiterals}/StringTemplate1.kt (63%) rename idea/idea-completion/testData/basic/common/{ => inStringLiterals}/StringTemplate2.kt (100%) create mode 100644 idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDot.kt create mode 100644 idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDotSomething.kt create mode 100644 idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot1.kt create mode 100644 idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot1.kt.after create mode 100644 idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot2.kt create mode 100644 idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot2.kt.after create mode 100644 idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot3.kt create mode 100644 idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot3.kt.after 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 88f0b4420b1..cd4ac67fe8f 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 @@ -54,6 +54,7 @@ import java.util.* class BasicCompletionSession( configuration: CompletionSessionConfiguration, parameters: CompletionParameters, + toFromOriginalFileMapper: ToFromOriginalFileMapper, resultSet: CompletionResultSet ) : CompletionSession(configuration, parameters, resultSet) { diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt index 9a52ba3ae02..69efd811dfa 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt @@ -21,6 +21,7 @@ import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.completion.CompletionSorter import com.intellij.codeInsight.completion.CompletionUtil import com.intellij.codeInsight.completion.impl.CamelHumpMatcher +import com.intellij.codeInsight.lookup.LookupElement import com.intellij.openapi.vfs.VirtualFile import com.intellij.patterns.StandardPatterns import com.intellij.psi.search.DelegatingGlobalSearchScope @@ -161,9 +162,6 @@ abstract class CompletionSession( return KotlinIndicesHelper(resolutionFacade, searchScope, filter, filterOutPrivate = !mayIncludeInaccessible) } - protected val toFromOriginalFileMapper: ToFromOriginalFileMapper - = ToFromOriginalFileMapper(parameters.originalFile as KtFile, position.containingFile as KtFile, parameters.offset) - // excludes top-level extensions except for ones declared in the current file - those that are fetched from indices protected val topLevelExtensionsExclude = object : DescriptorKindExclude() { val extensionsFromThisFile = file.declarations @@ -242,6 +240,10 @@ abstract class CompletionSession( return !collector.isResultEmpty } + public fun addLookupElementPostProcessor(processor: (LookupElement) -> LookupElement) { + collector.addLookupElementPostProcessor(processor) + } + protected abstract fun doComplete() protected abstract val descriptorKindFilter: DescriptorKindFilter? 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 0b7189f59aa..64efc5c71d2 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 @@ -17,6 +17,8 @@ package org.jetbrains.kotlin.idea.completion import com.intellij.codeInsight.completion.* +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupElementDecorator import com.intellij.openapi.editor.Document import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.util.Key @@ -51,6 +53,8 @@ public class KotlinCompletionContributor : CompletionContributor() { companion object { public val DEFAULT_DUMMY_IDENTIFIER: String = CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + "$" // add '$' to ignore context after the caret + + private val STRING_TEMPLATE_AFTER_DOT_REAL_START_OFFSET = OffsetKey.create("STRING_TEMPLATE_AFTER_DOT_REAL_START_OFFSET") } init { @@ -67,10 +71,27 @@ public class KotlinCompletionContributor : CompletionContributor() { val psiFile = context.getFile() if (psiFile !is KtFile) return + // this code will make replacement offset "modified" and prevents altering it by the code in CompletionProgressIndicator + context.replacementOffset = context.replacementOffset + val offset = context.getStartOffset() val tokenBefore = psiFile.findElementAt(Math.max(0, offset - 1)) - val dummyIdentifier = when { + if (offset > 0 && tokenBefore!!.node.elementType == KtTokens.REGULAR_STRING_PART && tokenBefore.text.startsWith(".")) { + val prev = tokenBefore.parent.prevSibling + if (prev != null && prev is KtSimpleNameStringTemplateEntry) { + val expression = prev.expression + if (expression != null) { + val prefix = tokenBefore.text.substring(0, offset - tokenBefore.startOffset) + context.dummyIdentifier = "{" + expression.text + prefix + CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + "}" + context.offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, expression.startOffset) + context.offsetMap.addOffset(STRING_TEMPLATE_AFTER_DOT_REAL_START_OFFSET, offset + 1) + return + } + } + } + + context.dummyIdentifier = when { context.getCompletionType() == CompletionType.SMART -> DEFAULT_DUMMY_IDENTIFIER PackageDirectiveCompletion.ACTIVATION_PATTERN.accepts(tokenBefore) -> PackageDirectiveCompletion.DUMMY_IDENTIFIER @@ -87,10 +108,6 @@ public class KotlinCompletionContributor : CompletionContributor() { ?: specialInArgumentListDummyIdentifier(tokenBefore) ?: DEFAULT_DUMMY_IDENTIFIER } - context.setDummyIdentifier(dummyIdentifier) - - // this code will make replacement offset "modified" and prevents altering it by the code in CompletionProgressIndicator - context.setReplacementOffset(context.getReplacementOffset()) if (context.getCompletionType() == CompletionType.SMART && !isAtEndOfLine(offset, context.getEditor().getDocument()) /* do not use parent expression if we are at the end of line - it's probably parsed incorrectly */) { val tokenAt = psiFile.findElementAt(Math.max(0, offset)) @@ -216,17 +233,38 @@ public class KotlinCompletionContributor : CompletionContributor() { } private fun performCompletion(parameters: CompletionParameters, result: CompletionResultSet) { - val position = parameters.getPosition() - val positionFile = position.containingFile - if (positionFile !is KtFile) return - if ((positionFile.originalFile as KtFile).doNotComplete ?: false) return + val position = parameters.position + val positionFile = position.containingFile as? KtFile ?: return + val originalFile = parameters.originalFile as KtFile + if (originalFile.doNotComplete ?: false) return + + val toFromOriginalFileMapper = ToFromOriginalFileMapper(originalFile, positionFile, parameters.offset) + + if (position.node.elementType == KtTokens.LONG_TEMPLATE_ENTRY_START) { + val expression = (position.parent as KtBlockStringTemplateEntry).expression as KtDotQualifiedExpression + val correctedPosition = (expression.selectorExpression as KtNameReferenceExpression).firstChild + val context = position.getUserData(CompletionContext.COMPLETION_CONTEXT_KEY)!! + val correctedOffset = context.offsetMap.getOffset(STRING_TEMPLATE_AFTER_DOT_REAL_START_OFFSET) + val correctedParameters = parameters.withPosition(correctedPosition, correctedOffset) + performCompletionWithOutOfBlockTracking(position) { + doComplete(correctedParameters, toFromOriginalFileMapper, result, + lookupElementPostProcessor = { wrapLookupElementForStringTemplateAfterDotCompletion(it) }) + } + return + } performCompletionWithOutOfBlockTracking(position) { - doComplete(parameters, position, result) + doComplete(parameters, toFromOriginalFileMapper, result) } } - private fun doComplete(parameters: CompletionParameters, position: PsiElement, result: CompletionResultSet) { + private fun doComplete( + parameters: CompletionParameters, + toFromOriginalFileMapper: ToFromOriginalFileMapper, + result: CompletionResultSet, + lookupElementPostProcessor: ((LookupElement) -> LookupElement)? = null + ) { + val position = parameters.position if (position.getNonStrictParentOfType() != null) { // don't stop here, allow other contributors to run return @@ -244,12 +282,20 @@ public class KotlinCompletionContributor : CompletionContributor() { if (PropertyKeyCompletion.perform(parameters, result)) return + fun addPostProcessor(session: CompletionSession) { + if (lookupElementPostProcessor != null) { + session.addLookupElementPostProcessor(lookupElementPostProcessor) + } + } + try { result.restartCompletionWhenNothingMatches() val configuration = CompletionSessionConfiguration(parameters) if (parameters.getCompletionType() == CompletionType.BASIC) { - val session = BasicCompletionSession(configuration, parameters, result) + val session = BasicCompletionSession(configuration, parameters, toFromOriginalFileMapper, result) + + addPostProcessor(session) if (parameters.isAutoPopup() && session.shouldDisableAutoPopup()) { result.stopHere() @@ -266,11 +312,16 @@ public class KotlinCompletionContributor : CompletionContributor() { completeJavaClassesNotToBeUsed = false, completeStaticMembers = parameters.invocationCount > 0 ) - BasicCompletionSession(newConfiguration, parameters, result).complete() + + val newSession = BasicCompletionSession(newConfiguration, parameters, toFromOriginalFileMapper, result) + addPostProcessor(newSession) + newSession.complete() } } else { - SmartCompletionSession(configuration, parameters, result).complete() + val session = SmartCompletionSession(configuration, parameters, toFromOriginalFileMapper, result) + addPostProcessor(session) + session.complete() } } catch (e: ProcessCanceledException) { @@ -278,6 +329,32 @@ public class KotlinCompletionContributor : CompletionContributor() { } } + private fun wrapLookupElementForStringTemplateAfterDotCompletion(lookupElement: LookupElement): LookupElement { + return object : LookupElementDecorator(lookupElement) { + override fun handleInsert(context: InsertionContext) { + val document = context.document + val startOffset = context.startOffset + + val psiDocumentManager = PsiDocumentManager.getInstance(context.project) + psiDocumentManager.commitAllDocuments() + + assert(startOffset > 1 && document.charsSequence[startOffset - 1] == '.') + val token = context.file.findElementAt(startOffset - 2)!! + assert(token.node.elementType == KtTokens.IDENTIFIER) + val nameRef = token.parent as KtNameReferenceExpression + assert(nameRef.parent is KtSimpleNameStringTemplateEntry) + + document.insertString(nameRef.startOffset, "{") + + val tailOffset = context.tailOffset + document.insertString(tailOffset, "}") + context.tailOffset = tailOffset + + super.handleInsert(context) + } + } + } + private fun shouldSuppressCompletion(parameters: CompletionParameters, prefixMatcher: PrefixMatcher): Boolean { val position = parameters.getPosition() val invocationCount = parameters.getInvocationCount() @@ -399,20 +476,6 @@ public class KotlinCompletionContributor : CompletionContributor() { return null } - private fun countParenthesisBalance(at: PsiElement, container: PsiElement): Int { - val stopAt = container.prevLeaf() - var current: PsiElement? = at - var balance = 0 - while (current != stopAt) { - when (current!!.getNode().getElementType()) { - KtTokens.LPAR -> balance++ - KtTokens.RPAR -> balance-- - } - current = current.prevLeaf() - } - return balance - } - private fun isInUnclosedSuperQualifier(tokenBefore: PsiElement?): Boolean { if (tokenBefore == null) return false val tokensToSkip = TokenSet.orSet(TokenSet.create(KtTokens.IDENTIFIER, KtTokens.DOT ), KtTokens.WHITE_SPACE_OR_COMMENT_BIT_SET) diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt index bd9c47e0c4a..b177228b78c 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt @@ -35,8 +35,12 @@ import org.jetbrains.kotlin.resolve.calls.callUtil.getCall import org.jetbrains.kotlin.resolve.calls.util.DelegatingCall import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter -class SmartCompletionSession(configuration: CompletionSessionConfiguration, parameters: CompletionParameters, resultSet: CompletionResultSet) -: CompletionSession(configuration, parameters, resultSet) { +class SmartCompletionSession( + configuration: CompletionSessionConfiguration, + parameters: CompletionParameters, + toFromOriginalFileMapper: ToFromOriginalFileMapper, + resultSet: CompletionResultSet +) : CompletionSession(configuration, parameters, resultSet) { override val descriptorKindFilter: DescriptorKindFilter by lazy { // we do not include SAM-constructors because they are handled separately and adding them requires iterating of java classes diff --git a/idea/idea-completion/testData/basic/common/StringTemplate1.kt b/idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplate1.kt similarity index 63% rename from idea/idea-completion/testData/basic/common/StringTemplate1.kt rename to idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplate1.kt index 7d5854b7f04..48562c3b74c 100644 --- a/idea/idea-completion/testData/basic/common/StringTemplate1.kt +++ b/idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplate1.kt @@ -1,5 +1,5 @@ fun foo(param: String) { - val s = "$" + val s = "$bla-bla-bla" } // EXIST: foo diff --git a/idea/idea-completion/testData/basic/common/StringTemplate2.kt b/idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplate2.kt similarity index 100% rename from idea/idea-completion/testData/basic/common/StringTemplate2.kt rename to idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplate2.kt diff --git a/idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDot.kt b/idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDot.kt new file mode 100644 index 00000000000..e1c372da5af --- /dev/null +++ b/idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDot.kt @@ -0,0 +1,7 @@ +fun foo(param: String) { + val s = "$param.bla-bla-bla" +} + +// EXIST: { itemText: "length", attributes: "" } +// EXIST: { itemText: "hashCode", attributes: "" } +// EXIST: { itemText: "capitalize", attributes: "bold" } diff --git a/idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDotSomething.kt b/idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDotSomething.kt new file mode 100644 index 00000000000..c2ede404cee --- /dev/null +++ b/idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDotSomething.kt @@ -0,0 +1,7 @@ +fun foo(param: String) { + val s = "$param.lbla-bla-bla" +} + +// EXIST: { itemText: "length", attributes: "" } +// ABSENT: hashCode +// EXIST: { itemText: "lastIndex", attributes: "" } diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot1.kt b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot1.kt new file mode 100644 index 00000000000..88f219f89e5 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot1.kt @@ -0,0 +1,6 @@ +fun foo(param: String) { + val s = "$param.bla-bla-bla" +} + +// ELEMENT: equals +// TAIL_TEXT: "(other: Any?)" \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot1.kt.after b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot1.kt.after new file mode 100644 index 00000000000..d322af287f4 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot1.kt.after @@ -0,0 +1,6 @@ +fun foo(param: String) { + val s = "${param.equals()}bla-bla-bla" +} + +// ELEMENT: equals +// TAIL_TEXT: "(other: Any?)" \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot2.kt b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot2.kt new file mode 100644 index 00000000000..5d5734d4eb6 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot2.kt @@ -0,0 +1,5 @@ +fun foo(param: String) { + val s = "$param.bla-bla-bla" +} + +// ELEMENT: hashCode \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot2.kt.after b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot2.kt.after new file mode 100644 index 00000000000..d053c6bc963 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot2.kt.after @@ -0,0 +1,5 @@ +fun foo(param: String) { + val s = "${param.hashCode()}bla-bla-bla" +} + +// ELEMENT: hashCode \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot3.kt b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot3.kt new file mode 100644 index 00000000000..1ded59365fb --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot3.kt @@ -0,0 +1,6 @@ +fun foo(param: String) { + val s = "$param.eqbla-bla-bla" +} + +// ELEMENT: equals +// TAIL_TEXT: "(other: Any?)" \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot3.kt.after b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot3.kt.after new file mode 100644 index 00000000000..d322af287f4 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot3.kt.after @@ -0,0 +1,6 @@ +fun foo(param: String) { + val s = "${param.equals()}bla-bla-bla" +} + +// ELEMENT: equals +// TAIL_TEXT: "(other: Any?)" \ 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 00676404cfb..5a850dfbd28 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 @@ -673,18 +673,6 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes doTest(fileName); } - @TestMetadata("StringTemplate1.kt") - public void testStringTemplate1() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/StringTemplate1.kt"); - doTest(fileName); - } - - @TestMetadata("StringTemplate2.kt") - public void testStringTemplate2() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/StringTemplate2.kt"); - doTest(fileName); - } - @TestMetadata("SubpackageInFun.kt") public void testSubpackageInFun() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/SubpackageInFun.kt"); @@ -1468,6 +1456,39 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes } } + @TestMetadata("idea/idea-completion/testData/basic/common/inStringLiterals") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class InStringLiterals extends AbstractJSBasicCompletionTest { + public void testAllFilesPresentInInStringLiterals() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/inStringLiterals"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("StringTemplate1.kt") + public void testStringTemplate1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplate1.kt"); + doTest(fileName); + } + + @TestMetadata("StringTemplate2.kt") + public void testStringTemplate2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplate2.kt"); + doTest(fileName); + } + + @TestMetadata("StringTemplateDot.kt") + public void testStringTemplateDot() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDot.kt"); + doTest(fileName); + } + + @TestMetadata("StringTemplateDotSomething.kt") + public void testStringTemplateDotSomething() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDotSomething.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/idea-completion/testData/basic/common/lambdaSignature") @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 717f1f80ec9..b73c7308496 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 @@ -673,18 +673,6 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT doTest(fileName); } - @TestMetadata("StringTemplate1.kt") - public void testStringTemplate1() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/StringTemplate1.kt"); - doTest(fileName); - } - - @TestMetadata("StringTemplate2.kt") - public void testStringTemplate2() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/StringTemplate2.kt"); - doTest(fileName); - } - @TestMetadata("SubpackageInFun.kt") public void testSubpackageInFun() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/SubpackageInFun.kt"); @@ -1468,6 +1456,39 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT } } + @TestMetadata("idea/idea-completion/testData/basic/common/inStringLiterals") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class InStringLiterals extends AbstractJvmBasicCompletionTest { + public void testAllFilesPresentInInStringLiterals() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/inStringLiterals"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("StringTemplate1.kt") + public void testStringTemplate1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplate1.kt"); + doTest(fileName); + } + + @TestMetadata("StringTemplate2.kt") + public void testStringTemplate2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplate2.kt"); + doTest(fileName); + } + + @TestMetadata("StringTemplateDot.kt") + public void testStringTemplateDot() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDot.kt"); + doTest(fileName); + } + + @TestMetadata("StringTemplateDotSomething.kt") + public void testStringTemplateDotSomething() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/inStringLiterals/StringTemplateDotSomething.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/idea-completion/testData/basic/common/lambdaSignature") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java index 8e1518e4347..9d27b9ae15c 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java @@ -591,6 +591,24 @@ public class BasicCompletionHandlerTestGenerated extends AbstractBasicCompletion doTest(fileName); } + @TestMetadata("AfterDot1.kt") + public void testAfterDot1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot1.kt"); + doTest(fileName); + } + + @TestMetadata("AfterDot2.kt") + public void testAfterDot2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot2.kt"); + doTest(fileName); + } + + @TestMetadata("AfterDot3.kt") + public void testAfterDot3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/stringTemplate/AfterDot3.kt"); + doTest(fileName); + } + public void testAllFilesPresentInStringTemplate() throws Exception { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/handlers/basic/stringTemplate"), Pattern.compile("^(.+)\\.kt$"), true); } diff --git a/idea/src/org/jetbrains/kotlin/idea/editor/KotlinTypedHandler.java b/idea/src/org/jetbrains/kotlin/idea/editor/KotlinTypedHandler.java index 1a3846f6caf..129db9fa8f7 100644 --- a/idea/src/org/jetbrains/kotlin/idea/editor/KotlinTypedHandler.java +++ b/idea/src/org/jetbrains/kotlin/idea/editor/KotlinTypedHandler.java @@ -47,6 +47,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.KtNodeTypes; import org.jetbrains.kotlin.lexer.KtTokens; import org.jetbrains.kotlin.psi.KtFile; +import org.jetbrains.kotlin.psi.KtSimpleNameStringTemplateEntry; public class KotlinTypedHandler extends TypedHandlerDelegate { private final static TokenSet CONTROL_FLOW_EXPRESSIONS = TokenSet.create( @@ -153,11 +154,18 @@ public class KotlinTypedHandler extends TypedHandlerDelegate { public boolean value(PsiFile file) { int offset = editor.getCaretModel().getOffset(); - PsiElement lastElement = file.findElementAt(offset - 1); - if (lastElement == null) return false; + PsiElement lastToken = file.findElementAt(offset - 1); + if (lastToken == null) return false; - IElementType elementType = lastElement.getNode().getElementType(); - return elementType == KtTokens.DOT || elementType == KtTokens.SAFE_ACCESS; + IElementType elementType = lastToken.getNode().getElementType(); + if (elementType == KtTokens.DOT || elementType == KtTokens.SAFE_ACCESS) return true; + + if (elementType == KtTokens.REGULAR_STRING_PART && lastToken.getTextRange().getStartOffset() == offset - 1) { + PsiElement prevSibling = lastToken.getParent().getPrevSibling(); + return prevSibling != null && prevSibling instanceof KtSimpleNameStringTemplateEntry; + } + + return false; } }); }