From 5fb90a8a810f60fd92a8a59b0375ddc52a26e43b Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Mon, 10 Dec 2018 18:15:25 +0300 Subject: [PATCH] Better caret position after `Insert curly braces around variable` (KT-28739) Introduce helper class and move caret to it's original position. #KT-28739 Fixed --- .../kotlin/idea/core/generateUtil.kt | 60 +++++++++++++++++++ .../InsertCurlyBracesToTemplateIntention.kt | 18 +++++- .../insertBrackets1.kt.after | 2 +- .../insertBrackets2.kt.after | 2 +- .../insertBrackets4.kt.after | 2 +- .../insertBrackets5.kt.after | 2 +- .../insertBracketsEnd.kt | 2 + .../insertBracketsEnd.kt.after | 2 + .../insertBracketsMiddle.kt | 2 + .../insertBracketsMiddle.kt.after | 2 + .../intentions/IntentionTestGenerated.java | 10 ++++ 11 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsEnd.kt create mode 100644 idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsEnd.kt.after create mode 100644 idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsMiddle.kt create mode 100644 idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsMiddle.kt.after diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/generateUtil.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/generateUtil.kt index eb2e654263a..280737be168 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/generateUtil.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/generateUtil.kt @@ -44,6 +44,66 @@ fun moveCaretIntoGeneratedElement(editor: Editor, element: PsiElement) { pointer.element?.let { moveCaretIntoGeneratedElementDocumentUnblocked(editor, it) } } +class RestoreCaret(beforeElement: T, val editor: Editor?) { + private val relativeOffset: Int + private val beforeElementTextLength: Int = beforeElement.textLength + + init { + relativeOffset = findRelativeOffset(beforeElement, editor) + } + + fun restoreCaret(afterElement: T, defaultOffset: ((element: T) -> Int)? = null) { + if (editor == null) return + val project = editor.project ?: return + + val pointer = SmartPointerManager.getInstance(project).createSmartPsiElementPointer(afterElement) + + val document = editor.document + PsiDocumentManager.getInstance(project).commitDocument(document) + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document) + + val afterElementChanged = pointer.element ?: return + + val offset = if (relativeOffset != -1 && afterElementChanged.textLength == beforeElementTextLength) { + afterElementChanged.startOffset + relativeOffset + } else { + if (defaultOffset != null) { + defaultOffset(afterElementChanged) + } else { + -1 + } + } + + if (offset == -1) { + return + } + + if (document.textLength > offset) { + editor.caretModel.moveToOffset(offset) + } + } + + companion object { + fun findRelativeOffset(element: PsiElement, editor: Editor?): Int { + if (editor != null) { + val singleCaret = editor.caretModel.allCarets.singleOrNull() + if (singleCaret != null) { + val caretOffset = singleCaret.offset + val textRange = element.textRange + if (textRange.startOffset <= caretOffset && caretOffset <= textRange.endOffset) { + val relative = caretOffset - element.startOffset + if (relative >= 0) { + return relative + } + } + } + } + + return -1 + } + } +} + private fun moveCaretIntoGeneratedElementDocumentUnblocked(editor: Editor, element: PsiElement): Boolean { // Inspired by GenerateMembersUtils.positionCaret() diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/InsertCurlyBracesToTemplateIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/InsertCurlyBracesToTemplateIntention.kt index c0cdc3e589a..941728e71d0 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/InsertCurlyBracesToTemplateIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/InsertCurlyBracesToTemplateIntention.kt @@ -18,16 +18,28 @@ package org.jetbrains.kotlin.idea.intentions import com.intellij.codeInsight.intention.LowPriorityAction import com.intellij.openapi.editor.Editor +import org.jetbrains.kotlin.idea.core.RestoreCaret import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.KtSimpleNameStringTemplateEntry +import org.jetbrains.kotlin.psi.KtStringTemplateEntryWithExpression +import org.jetbrains.kotlin.psi.psiUtil.endOffset -class InsertCurlyBracesToTemplateIntention : SelfTargetingOffsetIndependentIntention ( - KtSimpleNameStringTemplateEntry::class.java, "Insert curly braces around variable"), LowPriorityAction { +class InsertCurlyBracesToTemplateIntention : + SelfTargetingOffsetIndependentIntention( + KtSimpleNameStringTemplateEntry::class.java, "Insert curly braces around variable" + ), + LowPriorityAction { override fun isApplicableTo(element: KtSimpleNameStringTemplateEntry): Boolean = true override fun applyTo(element: KtSimpleNameStringTemplateEntry, editor: Editor?) { val expression = element.expression ?: return - element.replace(KtPsiFactory(element).createBlockStringTemplateEntry(expression)) + + with(RestoreCaret(expression, editor)) { + val wrapped = element.replace(KtPsiFactory(element).createBlockStringTemplateEntry(expression)) + val afterExpression = (wrapped as? KtStringTemplateEntryWithExpression)?.expression ?: return + + restoreCaret(afterExpression, defaultOffset = { it.endOffset }) + } } } diff --git a/idea/testData/intentions/insertCurlyBracesToTemplate/insertBrackets1.kt.after b/idea/testData/intentions/insertCurlyBracesToTemplate/insertBrackets1.kt.after index f0c125c138c..17532487535 100644 --- a/idea/testData/intentions/insertCurlyBracesToTemplate/insertBrackets1.kt.after +++ b/idea/testData/intentions/insertCurlyBracesToTemplate/insertBrackets1.kt.after @@ -1,5 +1,5 @@ // IS_APPLICABLE: true fun foo() { val x = 4 - val y = "${x}" + val y = "${x}" } \ No newline at end of file diff --git a/idea/testData/intentions/insertCurlyBracesToTemplate/insertBrackets2.kt.after b/idea/testData/intentions/insertCurlyBracesToTemplate/insertBrackets2.kt.after index 70b43e6560f..3f516a88df1 100644 --- a/idea/testData/intentions/insertCurlyBracesToTemplate/insertBrackets2.kt.after +++ b/idea/testData/intentions/insertCurlyBracesToTemplate/insertBrackets2.kt.after @@ -1,5 +1,5 @@ // IS_APPLICABLE: true fun foo() { val x = 4 - val y = "something${x}.somethingelse" + val y = "something${x}.somethingelse" } \ No newline at end of file diff --git a/idea/testData/intentions/insertCurlyBracesToTemplate/insertBrackets4.kt.after b/idea/testData/intentions/insertCurlyBracesToTemplate/insertBrackets4.kt.after index 2c50a0fa43a..604454f6b13 100644 --- a/idea/testData/intentions/insertCurlyBracesToTemplate/insertBrackets4.kt.after +++ b/idea/testData/intentions/insertCurlyBracesToTemplate/insertBrackets4.kt.after @@ -1,5 +1,5 @@ // IS_APPLICABLE: true fun foo() { val x = 4 - val y = "$x${x}$x" + val y = "$x${x}$x" } \ No newline at end of file diff --git a/idea/testData/intentions/insertCurlyBracesToTemplate/insertBrackets5.kt.after b/idea/testData/intentions/insertCurlyBracesToTemplate/insertBrackets5.kt.after index 28e07b6be19..7285489ca88 100644 --- a/idea/testData/intentions/insertCurlyBracesToTemplate/insertBrackets5.kt.after +++ b/idea/testData/intentions/insertCurlyBracesToTemplate/insertBrackets5.kt.after @@ -1,5 +1,5 @@ // IS_APPLICABLE: true fun foo() { val x = 4 - val y = """$x${x}$x""" + val y = """$x${x}$x""" } \ No newline at end of file diff --git a/idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsEnd.kt b/idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsEnd.kt new file mode 100644 index 00000000000..8c9f087e814 --- /dev/null +++ b/idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsEnd.kt @@ -0,0 +1,2 @@ +val test = "some" +val other = "text $test text" \ No newline at end of file diff --git a/idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsEnd.kt.after b/idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsEnd.kt.after new file mode 100644 index 00000000000..735c5a6c220 --- /dev/null +++ b/idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsEnd.kt.after @@ -0,0 +1,2 @@ +val test = "some" +val other = "text ${test} text" \ No newline at end of file diff --git a/idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsMiddle.kt b/idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsMiddle.kt new file mode 100644 index 00000000000..91b1d401174 --- /dev/null +++ b/idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsMiddle.kt @@ -0,0 +1,2 @@ +val test = "some" +val other = "text $test text" \ No newline at end of file diff --git a/idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsMiddle.kt.after b/idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsMiddle.kt.after new file mode 100644 index 00000000000..a372f2e8c76 --- /dev/null +++ b/idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsMiddle.kt.after @@ -0,0 +1,2 @@ +val test = "some" +val other = "text ${test} text" \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 6071112920f..8215b5724bd 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -9466,6 +9466,16 @@ public class IntentionTestGenerated extends AbstractIntentionTest { public void testInsertBrackets5() throws Exception { runTest("idea/testData/intentions/insertCurlyBracesToTemplate/insertBrackets5.kt"); } + + @TestMetadata("insertBracketsEnd.kt") + public void testInsertBracketsEnd() throws Exception { + runTest("idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsEnd.kt"); + } + + @TestMetadata("insertBracketsMiddle.kt") + public void testInsertBracketsMiddle() throws Exception { + runTest("idea/testData/intentions/insertCurlyBracesToTemplate/insertBracketsMiddle.kt"); + } } @TestMetadata("idea/testData/intentions/insertExplicitTypeArguments")