From 169a22226fcbc2eb2194fe64dea47a53b093ffe7 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Wed, 8 Nov 2017 12:37:10 +0300 Subject: [PATCH] Rename: Support inplace rename of class via primary constructor #KT-20146 Fixed #KT-21371 Fixed --- .../KotlinRefactoringSupportProvider.kt | 3 +- .../KotlinMemberInplaceRenameHandler.kt | 31 ++++++++++++++++--- .../rename/KotlinRenameDispatcherHandler.kt | 2 +- .../classByPrimaryConstructor/after/test.kt | 2 ++ .../classByPrimaryConstructor/before/test.kt | 2 ++ .../classByPrimaryConstructor.test | 5 +++ .../after/test.kt | 2 ++ .../before/test.kt | 2 ++ .../classByPrimaryConstructorWithSpace.test | 5 +++ .../refactoring/rename/AbstractRenameTest.kt | 16 ++++++++-- .../rename/RenameTestGenerated.java | 12 +++++++ 11 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 idea/testData/refactoring/rename/classByPrimaryConstructor/after/test.kt create mode 100644 idea/testData/refactoring/rename/classByPrimaryConstructor/before/test.kt create mode 100644 idea/testData/refactoring/rename/classByPrimaryConstructor/classByPrimaryConstructor.test create mode 100644 idea/testData/refactoring/rename/classByPrimaryConstructorWithSpace/after/test.kt create mode 100644 idea/testData/refactoring/rename/classByPrimaryConstructorWithSpace/before/test.kt create mode 100644 idea/testData/refactoring/rename/classByPrimaryConstructorWithSpace/classByPrimaryConstructorWithSpace.test diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringSupportProvider.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringSupportProvider.kt index ee3e39db354..d48ee26fd86 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringSupportProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringSupportProvider.kt @@ -76,7 +76,8 @@ class KotlinRefactoringSupportProvider : RefactoringSupportProvider() { return false } - override fun isMemberInplaceRenameAvailable(element: PsiElement, context: PsiElement?) = element is KtNamedDeclaration + override fun isMemberInplaceRenameAvailable(element: PsiElement, context: PsiElement?) = + element is KtNamedDeclaration && element.nameIdentifier != null override fun getChangeSignatureHandler() = KotlinChangeSignatureHandler() diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KotlinMemberInplaceRenameHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KotlinMemberInplaceRenameHandler.kt index 0bc46c02829..11d61ffebdf 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KotlinMemberInplaceRenameHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KotlinMemberInplaceRenameHandler.kt @@ -24,6 +24,7 @@ import com.intellij.refactoring.rename.inplace.VariableInplaceRenameHandler import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer import org.jetbrains.kotlin.idea.core.unquote import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtPrimaryConstructor class KotlinMemberInplaceRenameHandler : MemberInplaceRenameHandler() { companion object { @@ -53,14 +54,34 @@ class KotlinMemberInplaceRenameHandler : MemberInplaceRenameHandler() { } } + private fun PsiElement.substitute(): PsiElement { + if (this is KtPrimaryConstructor) return getContainingClassOrObject() + return this + } + override fun createMemberRenamer(element: PsiElement, elementToRename: PsiNameIdentifierOwner, editor: Editor): MemberInplaceRenamer { - val currentName = elementToRename.nameIdentifier?.text ?: "" - return RenamerImpl(elementToRename, element, editor, currentName, currentName) + val currentElementToRename = elementToRename.substitute() as PsiNameIdentifierOwner + val nameIdentifier = currentElementToRename.nameIdentifier + + // Move caret if constructor range doesn't intersect with the one of the containing class + val offset = editor.caretModel.offset + val editorPsiFile = PsiDocumentManager.getInstance(element.project).getPsiFile(editor.document) + if (nameIdentifier != null + && editorPsiFile == elementToRename.containingFile + && elementToRename is KtPrimaryConstructor + && offset !in nameIdentifier.textRange + && offset in elementToRename.textRange) { + editor.caretModel.moveToOffset(nameIdentifier.textOffset) + } + + val currentName = nameIdentifier?.text ?: "" + return RenamerImpl(currentElementToRename, element, editor, currentName, currentName) } override fun isAvailable(element: PsiElement?, editor: Editor, file: PsiFile): Boolean { - return element is KtElement && - !variableInplaceHandler.isAvailable(element, editor, file) && - super.isAvailable(element, editor, file) + val currentElement = element?.substitute() + return currentElement is KtElement && + !variableInplaceHandler.isAvailable(currentElement, editor, file) && + super.isAvailable(currentElement, editor, file) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KotlinRenameDispatcherHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KotlinRenameDispatcherHandler.kt index 4a4d3e61776..7ef8e91c293 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KotlinRenameDispatcherHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KotlinRenameDispatcherHandler.kt @@ -34,7 +34,7 @@ class KotlinRenameDispatcherHandler : RenameHandler { private val handlers: Array get() = Extensions.getExtensions(EP_NAME) } - private fun getRenameHandler(dataContext: DataContext?): RenameHandler? { + internal fun getRenameHandler(dataContext: DataContext?): RenameHandler? { val availableHandlers = handlers.filterTo(LinkedHashSet()) { it.isRenaming(dataContext) } availableHandlers.singleOrNull()?.let { return it } availableHandlers.firstIsInstanceOrNull()?.let { availableHandlers -= it } diff --git a/idea/testData/refactoring/rename/classByPrimaryConstructor/after/test.kt b/idea/testData/refactoring/rename/classByPrimaryConstructor/after/test.kt new file mode 100644 index 00000000000..58eb5137e2c --- /dev/null +++ b/idea/testData/refactoring/rename/classByPrimaryConstructor/after/test.kt @@ -0,0 +1,2 @@ +open class Bar(val foo: Boolean) +class E : Bar(false) \ No newline at end of file diff --git a/idea/testData/refactoring/rename/classByPrimaryConstructor/before/test.kt b/idea/testData/refactoring/rename/classByPrimaryConstructor/before/test.kt new file mode 100644 index 00000000000..682cc63ea85 --- /dev/null +++ b/idea/testData/refactoring/rename/classByPrimaryConstructor/before/test.kt @@ -0,0 +1,2 @@ +open class Foo/*rename*/(val foo: Boolean) +class E : Foo(false) \ No newline at end of file diff --git a/idea/testData/refactoring/rename/classByPrimaryConstructor/classByPrimaryConstructor.test b/idea/testData/refactoring/rename/classByPrimaryConstructor/classByPrimaryConstructor.test new file mode 100644 index 00000000000..86f96713269 --- /dev/null +++ b/idea/testData/refactoring/rename/classByPrimaryConstructor/classByPrimaryConstructor.test @@ -0,0 +1,5 @@ +{ + "type": "AUTO_DETECT", + "mainFile": "test.kt", + "newName": "Bar" +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/classByPrimaryConstructorWithSpace/after/test.kt b/idea/testData/refactoring/rename/classByPrimaryConstructorWithSpace/after/test.kt new file mode 100644 index 00000000000..58eb5137e2c --- /dev/null +++ b/idea/testData/refactoring/rename/classByPrimaryConstructorWithSpace/after/test.kt @@ -0,0 +1,2 @@ +open class Bar(val foo: Boolean) +class E : Bar(false) \ No newline at end of file diff --git a/idea/testData/refactoring/rename/classByPrimaryConstructorWithSpace/before/test.kt b/idea/testData/refactoring/rename/classByPrimaryConstructorWithSpace/before/test.kt new file mode 100644 index 00000000000..45bb328e13a --- /dev/null +++ b/idea/testData/refactoring/rename/classByPrimaryConstructorWithSpace/before/test.kt @@ -0,0 +1,2 @@ +open class Foo /*rename*/(val foo: Boolean) +class E : Foo(false) \ No newline at end of file diff --git a/idea/testData/refactoring/rename/classByPrimaryConstructorWithSpace/classByPrimaryConstructorWithSpace.test b/idea/testData/refactoring/rename/classByPrimaryConstructorWithSpace/classByPrimaryConstructorWithSpace.test new file mode 100644 index 00000000000..86f96713269 --- /dev/null +++ b/idea/testData/refactoring/rename/classByPrimaryConstructorWithSpace/classByPrimaryConstructorWithSpace.test @@ -0,0 +1,5 @@ +{ + "type": "AUTO_DETECT", + "mainFile": "test.kt", + "newName": "Bar" +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/AbstractRenameTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/AbstractRenameTest.kt index b11632466ad..5ba1795d857 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/AbstractRenameTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/AbstractRenameTest.kt @@ -42,11 +42,13 @@ import com.intellij.psi.search.GlobalSearchScope import com.intellij.refactoring.BaseRefactoringProcessor import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException import com.intellij.refactoring.rename.* +import com.intellij.refactoring.rename.inplace.VariableInplaceRenameHandler import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory import com.intellij.refactoring.util.CommonRefactoringUtil.RefactoringErrorHintException import com.intellij.testFramework.LightProjectDescriptor import com.intellij.testFramework.PlatformTestUtil import com.intellij.testFramework.UsefulTestCase +import com.intellij.testFramework.fixtures.CodeInsightTestUtil import org.jetbrains.kotlin.asJava.finder.KtLightPackage import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime import org.jetbrains.kotlin.descriptors.ClassDescriptor @@ -349,9 +351,19 @@ abstract class AbstractRenameTest : KotlinLightCodeInsightFixtureTestCase() { else -> null } } - val handler = RenameHandlerRegistry.getInstance().getRenameHandler(dataContext) ?: return@doTestCommittingDocuments + var handler = RenameHandlerRegistry.getInstance().getRenameHandler(dataContext) ?: return@doTestCommittingDocuments Assert.assertTrue(handler.isAvailableOnDataContext(dataContext)) - handler.invoke(project, editor, psiFile, dataContext) + if (handler is KotlinRenameDispatcherHandler) { + handler = handler.getRenameHandler(dataContext)!! + } + + if (handler is VariableInplaceRenameHandler) { + val elementToRename = psiFile.findElementAt(currentCaret.offset)!!.getNonStrictParentOfType()!! + CodeInsightTestUtil.doInlineRename(handler, newName, editor, elementToRename) + } + else { + handler.invoke(project, editor, psiFile, dataContext) + } } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java index a80d16cb1e8..847c4a42ff8 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java @@ -168,6 +168,18 @@ public class RenameTestGenerated extends AbstractRenameTest { doTest(fileName); } + @TestMetadata("classByPrimaryConstructor/classByPrimaryConstructor.test") + public void testClassByPrimaryConstructor_ClassByPrimaryConstructor() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/classByPrimaryConstructor/classByPrimaryConstructor.test"); + doTest(fileName); + } + + @TestMetadata("classByPrimaryConstructorWithSpace/classByPrimaryConstructorWithSpace.test") + public void testClassByPrimaryConstructorWithSpace_ClassByPrimaryConstructorWithSpace() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/classByPrimaryConstructorWithSpace/classByPrimaryConstructorWithSpace.test"); + doTest(fileName); + } + @TestMetadata("classUsagesInTextFiles/classUsagesInTextFiles.test") public void testClassUsagesInTextFiles_ClassUsagesInTextFiles() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/classUsagesInTextFiles/classUsagesInTextFiles.test");