diff --git a/idea/src/org/jetbrains/kotlin/idea/editor/KotlinEnterHandler.kt b/idea/src/org/jetbrains/kotlin/idea/editor/KotlinEnterHandler.kt index af430f67122..564bb991faa 100644 --- a/idea/src/org/jetbrains/kotlin/idea/editor/KotlinEnterHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/editor/KotlinEnterHandler.kt @@ -16,24 +16,26 @@ package org.jetbrains.kotlin.idea.editor -import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter -import com.intellij.psi.PsiFile -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.util.Ref -import com.intellij.openapi.actionSystem.DataContext -import com.intellij.openapi.editor.actionSystem.EditorActionHandler -import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegate import com.intellij.codeInsight.CodeInsightSettings -import com.intellij.psi.PsiDocumentManager -import com.intellij.psi.codeStyle.CodeStyleManager -import org.jetbrains.kotlin.psi.JetFile -import com.intellij.util.IncorrectOperationException -import org.jetbrains.kotlin.lexer.JetTokens -import org.jetbrains.kotlin.psi.JetFunctionLiteral -import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils +import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegate +import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter +import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.diagnostic.Logger -import com.intellij.psi.tree.TokenSet +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.actionSystem.EditorActionHandler +import com.intellij.openapi.util.Ref +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiFile import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.codeStyle.CodeStyleManager +import com.intellij.psi.tree.TokenSet +import com.intellij.util.IncorrectOperationException +import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils +import org.jetbrains.kotlin.lexer.JetTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.isSingleQuoted public class KotlinEnterHandler: EnterHandlerDelegateAdapter() { companion object { @@ -50,6 +52,11 @@ public class KotlinEnterHandler: EnterHandlerDelegateAdapter() { originalHandler: EditorActionHandler? ): EnterHandlerDelegate.Result? { if (file !is JetFile) return EnterHandlerDelegate.Result.Continue + + if (preprocessEnterInStringLiteral(file, editor, caretOffsetRef, caretAdvance)) { + return EnterHandlerDelegate.Result.DefaultForceIndent + } + if (!CodeInsightSettings.getInstance()!!.SMART_INDENT_ON_ENTER) return EnterHandlerDelegate.Result.Continue val document = editor.getDocument() @@ -84,4 +91,36 @@ public class KotlinEnterHandler: EnterHandlerDelegateAdapter() { return EnterHandlerDelegate.Result.Continue } + + // We can't use the core platform logic (EnterInStringLiteralHandler) because it assumes that the string + // is a single token and the first character of the token is an opening quote. In the case of Kotlin, + // the opening quote is a separate token and the first character of the string token is just a random letter. + private fun preprocessEnterInStringLiteral(psiFile: PsiFile, + editor: Editor, + caretOffsetRef: Ref, + caretAdvanceRef: Ref): Boolean { + var caretOffset = caretOffsetRef.get() + val psiAtOffset = psiFile.findElementAt(caretOffset) ?: return false + val stringTemplate = psiAtOffset.getStrictParentOfType() ?: return false + if (!stringTemplate.isSingleQuoted()) return false + val tokenType = psiAtOffset.getNode().getElementType() + when (tokenType) { + JetTokens.CLOSING_QUOTE, JetTokens.REGULAR_STRING_PART, JetTokens.ESCAPE_SEQUENCE, + JetTokens.SHORT_TEMPLATE_ENTRY_START, JetTokens.LONG_TEMPLATE_ENTRY_START -> { + val doc = editor.getDocument() + var caretAdvance = 1 + if (stringTemplate.getParent() is JetDotQualifiedExpression) { + doc.insertString(stringTemplate.getTextRange().getEndOffset(), ")") + doc.insertString(stringTemplate.getTextRange().getStartOffset(), "(") + caretOffset++ + caretAdvance++ + } + doc.insertString(caretOffset, "\" + \"") + caretOffsetRef.set(caretOffset + 3) + caretAdvanceRef.set(caretAdvance) + return true + } + } + return false + } } diff --git a/idea/src/org/jetbrains/kotlin/idea/editor/KotlinQuoteHandler.kt b/idea/src/org/jetbrains/kotlin/idea/editor/KotlinQuoteHandler.kt index 6d662257aa7..17e1c0d4503 100644 --- a/idea/src/org/jetbrains/kotlin/idea/editor/KotlinQuoteHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/editor/KotlinQuoteHandler.kt @@ -53,6 +53,11 @@ public class KotlinQuoteHandler : QuoteHandler { override fun isInsideLiteral(iterator: HighlighterIterator): Boolean { val tokenType = iterator.getTokenType() - return tokenType == JetTokens.REGULAR_STRING_PART || tokenType == JetTokens.OPEN_QUOTE || tokenType == JetTokens.CLOSING_QUOTE || tokenType == JetTokens.SHORT_TEMPLATE_ENTRY_START || tokenType == JetTokens.LONG_TEMPLATE_ENTRY_END || tokenType == JetTokens.LONG_TEMPLATE_ENTRY_START + return tokenType == JetTokens.REGULAR_STRING_PART || + tokenType == JetTokens.OPEN_QUOTE || + tokenType == JetTokens.CLOSING_QUOTE || + tokenType == JetTokens.SHORT_TEMPLATE_ENTRY_START || + tokenType == JetTokens.LONG_TEMPLATE_ENTRY_END || + tokenType == JetTokens.LONG_TEMPLATE_ENTRY_START } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/editor/TypedHandlerTest.kt b/idea/tests/org/jetbrains/kotlin/idea/editor/TypedHandlerTest.kt index e0d80e6a61f..427eb33278e 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/editor/TypedHandlerTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/editor/TypedHandlerTest.kt @@ -234,6 +234,54 @@ public class TypedHandlerTest : LightCodeInsightTestCase() { """fun f() { val a = f() }""" ) + public fun testSplitStringByEnter(): Unit = doCharTypeTest( + '\n', + """val s = "foobar"""", + "val s = \"foo\" +\n" + + " \"bar\"" + ) + + public fun testSplitStringByEnter_Empty(): Unit = doCharTypeTest( + '\n', + """val s = """"", + "val s = \"\" +\n" + + " \"\"" + ) + + public fun testSplitStringByEnter_BeforeEscapeSequence(): Unit = doCharTypeTest( + '\n', + """val s = "foo\nbar"""", + "val s = \"foo\" +\n" + + " \"\\nbar\"" + ) + + public fun testSplitStringByEnter_BeforeSubstitution(): Unit = doCharTypeTest( + '\n', + """val s = "foo${amp}bar"""", + "val s = \"foo\" +\n" + + " \"${amp}bar\"" + ) + + public fun testSplitStringByEnter_AddParentheses(): Unit = doCharTypeTest( + '\n', + """val l = "foobar".length()""", + "val l = (\"foo\" +\n" + + " \"bar\").length()" + ) + + public fun testSplitStringByEnter_ExistingParentheses(): Unit = doCharTypeTest( + '\n', + """val l = ("asdf" + "foobar").length()""", + "val l = (\"asdf\" + \"foo\" +\n" + + " \"bar\").length()" + ) + + public fun testSplitStringByEnter_TripleQuotedString(): Unit = doCharTypeTest( + '\n', + "val l = \"\"\"foobar\"\"\"", + "val l = \"\"\"foo\nbar\"\"\"" + ) + public fun testTypeLtInFunDeclaration() { doLtGtTest("fun ") }