Rename: Support inplace rename of class via primary constructor

#KT-20146 Fixed
 #KT-21371 Fixed
This commit is contained in:
Alexey Sedunov
2017-11-08 12:37:10 +03:00
parent 2792e0c412
commit 169a22226f
11 changed files with 73 additions and 9 deletions
@@ -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()
@@ -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)
}
}
@@ -34,7 +34,7 @@ class KotlinRenameDispatcherHandler : RenameHandler {
private val handlers: Array<out RenameHandler> 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<KotlinMemberInplaceRenameHandler>()?.let { availableHandlers -= it }
@@ -0,0 +1,2 @@
open class Bar(val foo: Boolean)
class E : Bar(false)
@@ -0,0 +1,2 @@
open class Foo/*rename*/(val foo: Boolean)
class E : Foo(false)
@@ -0,0 +1,5 @@
{
"type": "AUTO_DETECT",
"mainFile": "test.kt",
"newName": "Bar"
}
@@ -0,0 +1,2 @@
open class Bar(val foo: Boolean)
class E : Bar(false)
@@ -0,0 +1,2 @@
open class Foo /*rename*/(val foo: Boolean)
class E : Foo(false)
@@ -0,0 +1,5 @@
{
"type": "AUTO_DETECT",
"mainFile": "test.kt",
"newName": "Bar"
}
@@ -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<PsiNamedElement>()!!
CodeInsightTestUtil.doInlineRename(handler, newName, editor, elementToRename)
}
else {
handler.invoke(project, editor, psiFile, dataContext)
}
}
}
@@ -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");