diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/KotlinMoveTarget.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/KotlinMoveTarget.kt index c34ace14bd1..54df326fd24 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/KotlinMoveTarget.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/KotlinMoveTarget.kt @@ -28,33 +28,21 @@ import kotlin.properties.Delegates public trait KotlinMoveTarget { val packageWrapper: PackageWrapper? - fun getOrCreateTargetPsi(originalPsi: PsiElement): PsiElement? - fun getTargetPsiIfExists(originalPsi: PsiElement): PsiElement? + fun getOrCreateTargetPsi(originalPsi: PsiElement): PsiFile? + fun getTargetPsiIfExists(originalPsi: PsiElement): PsiFile? // Check possible errors and return corresponding message, or null if no errors are detected fun verify(file: PsiFile): String? } -public class MoveDestinationKotlinMoveTarget(val moveDestination: MoveDestination): KotlinMoveTarget { - override val packageWrapper: PackageWrapper? = moveDestination.getTargetPackage() - - override fun getOrCreateTargetPsi(originalPsi: PsiElement): PsiElement? = - moveDestination.getTargetDirectory(originalPsi.getContainingFile()) - - override fun getTargetPsiIfExists(originalPsi: PsiElement): PsiElement? = - moveDestination.getTargetIfExists(originalPsi.getContainingFile()) - - override fun verify(file: PsiFile): String? = moveDestination.verify(file) -} - public class JetFileKotlinMoveTarget(val targetFile: JetFile): KotlinMoveTarget { override val packageWrapper: PackageWrapper? = targetFile.getPackageFqName().asString().let { packageName -> PackageWrapper(PsiManager.getInstance(targetFile.getProject()), packageName) } - override fun getOrCreateTargetPsi(originalPsi: PsiElement): PsiElement? = targetFile + override fun getOrCreateTargetPsi(originalPsi: PsiElement) = targetFile - override fun getTargetPsiIfExists(originalPsi: PsiElement): PsiElement? = targetFile + override fun getTargetPsiIfExists(originalPsi: PsiElement) = targetFile // No additional verification is needed override fun verify(file: PsiFile): String? = null @@ -68,9 +56,9 @@ public class DeferredJetFileKotlinMoveTarget( override val packageWrapper: PackageWrapper = PackageWrapper(PsiManager.getInstance(project), packageFqName.asString()) - override fun getOrCreateTargetPsi(originalPsi: PsiElement): PsiElement? = createdFile + override fun getOrCreateTargetPsi(originalPsi: PsiElement) = createdFile - override fun getTargetPsiIfExists(originalPsi: PsiElement): PsiElement? = null + override fun getTargetPsiIfExists(originalPsi: PsiElement) = null // No additional verification is needed override fun verify(file: PsiFile): String? = null diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/MoveKotlinTopLevelDeclarationsHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/MoveKotlinTopLevelDeclarationsHandler.kt index 418681c1f17..ac911138530 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/MoveKotlinTopLevelDeclarationsHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/MoveKotlinTopLevelDeclarationsHandler.kt @@ -16,54 +16,32 @@ package org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations -import com.intellij.refactoring.move.MoveHandlerDelegate import com.intellij.openapi.actionSystem.DataContext -import com.intellij.openapi.project.Project -import com.intellij.psi.PsiElement -import com.intellij.refactoring.move.MoveCallback -import com.intellij.psi.PsiReference -import com.intellij.openapi.editor.Editor -import org.jetbrains.kotlin.psi.JetFile -import org.jetbrains.kotlin.idea.core.refactoring.isInJavaSourceRoot -import org.jetbrains.kotlin.psi.JetObjectDeclaration -import org.jetbrains.kotlin.psi.JetClass -import com.intellij.psi.PsiPackage -import com.intellij.psi.PsiDirectory -import org.jetbrains.kotlin.psi.psiUtil.getPackage -import org.jetbrains.kotlin.idea.refactoring.move.getFileNameAfterMove -import com.intellij.refactoring.RefactoringBundle -import com.intellij.refactoring.util.CommonRefactoringUtil -import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesImpl -import com.intellij.refactoring.JavaRefactoringSettings import com.intellij.openapi.actionSystem.LangDataKeys -import org.jetbrains.kotlin.psi.JetNamedFunction -import org.jetbrains.kotlin.psi.JetProperty -import org.jetbrains.kotlin.psi.JetNamedDeclaration +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiPackage +import com.intellij.psi.PsiReference +import com.intellij.refactoring.JavaRefactoringSettings +import com.intellij.refactoring.RefactoringBundle +import com.intellij.refactoring.move.MoveCallback +import com.intellij.refactoring.move.MoveHandlerDelegate +import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesImpl +import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil +import com.intellij.refactoring.util.CommonRefactoringUtil +import org.jetbrains.kotlin.idea.core.refactoring.isInJavaSourceRoot import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.ui.MoveKotlinTopLevelDeclarationsDialog -import java.util.* +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getPackage +import java.util.HashSet +import java.util.LinkedHashSet public class MoveKotlinTopLevelDeclarationsHandler : MoveHandlerDelegate() { private fun doMoveWithCheck( project: Project, elements: Array, targetContainer: PsiElement?, callback: MoveCallback? ): Boolean { - fun checkNameConflicts(): Boolean { - val fileNames = HashSet() - for (element in elements) { - if (element !is JetNamedDeclaration) continue - - if (!fileNames.add(element.getFileNameAfterMove())) { - val message = RefactoringBundle.getCannotRefactorMessage( - RefactoringBundle.message("there.are.going.to.be.multiple.destination.files.with.the.same.name") - ) - CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("move.title"), message, null, project) - return false - } - } - - return true - } - - if (!checkNameConflicts()) return false if (!CommonRefactoringUtil.checkReadOnlyStatusRecursively(project, elements.toList(), true)) return false [suppress("UNCHECKED_CAST")] diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/MoveKotlinTopLevelDeclarationsProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/MoveKotlinTopLevelDeclarationsProcessor.kt index 0d167e434d2..a1c34ad5d00 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/MoveKotlinTopLevelDeclarationsProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/MoveKotlinTopLevelDeclarationsProcessor.kt @@ -238,16 +238,8 @@ public class MoveKotlinTopLevelDeclarationsProcessor( val file = declaration.getContainingFile() as? JetFile assert(file != null) { "${declaration.javaClass}: ${declaration.getText()}" } - val targetPsi = moveTarget.getOrCreateTargetPsi(declaration) - val targetFile = - if (targetPsi is PsiDirectory) { - val targetFileName = declaration.getFileNameAfterMove() - targetPsi.findFile(targetFileName) ?: createKotlinFile(targetFileName, targetPsi) - } - else targetPsi - - assert(targetFile is JetFile) { "Couldn't create Kotlin file for: ${declaration.javaClass}: ${declaration.getText()}" } - targetFile as JetFile + val targetFile = moveTarget.getOrCreateTargetPsi(declaration) as? JetFile + ?: throw AssertionError("Couldn't create Kotlin file for: ${declaration.javaClass}: ${declaration.getText()}") if (options.updateInternalReferences) { val packageNameInfo = PackageNameInfo(file!!.getPackageFqName(), targetFile.getPackageFqName()) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.form b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.form index 84bbf6f1dbd..b7a570fe9cb 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.form +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.form @@ -26,7 +26,7 @@ - + @@ -50,13 +50,13 @@ - + - + @@ -65,7 +65,7 @@ - + @@ -89,7 +89,7 @@ - + @@ -98,13 +98,33 @@ - + + + + + + + + + + + + + + + + + + + + + diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.java index 76e7b57526f..06de265dbd6 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.java @@ -36,6 +36,7 @@ import com.intellij.refactoring.PackageWrapper; import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.classMembers.DependencyMemberInfoModel; import com.intellij.refactoring.classMembers.MemberInfoChange; +import com.intellij.refactoring.classMembers.MemberInfoChangeListener; import com.intellij.refactoring.classMembers.UsesMemberDependencyGraph; import com.intellij.refactoring.move.MoveCallback; import com.intellij.refactoring.move.MoveHandler; @@ -56,6 +57,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.idea.core.refactoring.RefactoringPackage; import org.jetbrains.kotlin.idea.refactoring.JetRefactoringBundle; import org.jetbrains.kotlin.idea.refactoring.KotlinMemberInfo; +import org.jetbrains.kotlin.idea.refactoring.move.MovePackage; import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.*; import org.jetbrains.kotlin.idea.refactoring.ui.KotlinMemberSelectionPanel; import org.jetbrains.kotlin.idea.refactoring.ui.KotlinMemberSelectionTable; @@ -68,6 +70,7 @@ import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; +import java.util.Collection; import java.util.List; import java.util.Set; @@ -101,6 +104,8 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { private JRadioButton rbMoveToFile; private TextFieldWithBrowseButton fileChooser; private JPanel memberInfoPanel; + private JTextField tfFileNameInPackage; + private JCheckBox cbSpecifyFileNameInPackage; private KotlinMemberSelectionTable memberTable; private final JetFile sourceFile; @@ -143,7 +148,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { } private void initMemberInfo(@NotNull final Set elementsToMove) { - List memberInfos = KotlinPackage.map( + final List memberInfos = KotlinPackage.map( KotlinPackage.filterIsInstance(sourceFile.getDeclarations(), JetNamedDeclaration.class), new Function1() { @Override @@ -160,7 +165,41 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { memberInfoModel.memberInfoChanged(new MemberInfoChange(memberInfos)); selectionPanel.getTable().setMemberInfoModel(memberInfoModel); selectionPanel.getTable().addMemberInfoChangeListener(memberInfoModel); + selectionPanel.getTable().addMemberInfoChangeListener( + new MemberInfoChangeListener() { + private boolean shouldUpdateFileNameField(final Collection changedMembers) { + if (!tfFileNameInPackage.isEnabled()) return true; + + Collection previousDeclarations = KotlinPackage.filterNotNull( + KotlinPackage.map( + memberInfos, + new Function1() { + @Override + public JetNamedDeclaration invoke(KotlinMemberInfo info) { + return changedMembers.contains(info) != info.isChecked() ? info.getMember() : null; + } + } + ) + ); + String suggestedText = previousDeclarations.isEmpty() ? "" : MovePackage.guessNewFileName(previousDeclarations); + return tfFileNameInPackage.getText().equals(suggestedText); + } + + @Override + public void memberInfoChanged(MemberInfoChange event) { + // Update file name field only if it user hasn't changed it to some non-default value + if (shouldUpdateFileNameField(event.getChangedMembers())) { + updateSuggestedFileName(); + } + } + } + ); memberInfoPanel.add(selectionPanel, BorderLayout.CENTER); + + } + + private void updateSuggestedFileName() { + tfFileNameInPackage.setText(MovePackage.guessNewFileName(getSelectedElementsToMove())); } private void initPackageChooser(String targetPackageName, PsiDirectory targetDirectory) { @@ -179,6 +218,15 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { }, classPackageChooser.getChildComponent() ); + + cbSpecifyFileNameInPackage.addActionListener( + new ActionListener() { + @Override + public void actionPerformed(@NotNull ActionEvent e) { + tfFileNameInPackage.setEnabled(cbSpecifyFileNameInPackage.isSelected()); + } + } + ); } private void initSearchOptions(boolean searchInComments, boolean searchForTextOccurences) { @@ -257,6 +305,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { classPackageChooser.setEnabled(moveToPackage); fileChooser.setEnabled(!moveToPackage); UIUtil.setEnabled(targetPanel, moveToPackage && hasAnySourceRoots(), true); + updateSuggestedFileName(); validateButtons(); } @@ -292,8 +341,20 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { if (ret != Messages.YES) return null; } - MoveDestination moveDestination = ((DestinationFolderComboBox) destinationFolderCB).selectDirectory(targetPackage, false); - return moveDestination != null ? new MoveDestinationKotlinMoveTarget(moveDestination) : null; + final MoveDestination moveDestination = ((DestinationFolderComboBox) destinationFolderCB).selectDirectory(targetPackage, false); + if (moveDestination == null) return null; + + return new DeferredJetFileKotlinMoveTarget( + myProject, + new FqName(targetPackage.getQualifiedName()), + new Function0() { + @Override + public JetFile invoke() { + PsiDirectory directory = moveDestination.getTargetDirectory(sourceFile); + return RefactoringPackage.getOrCreateKotlinFile(tfFileNameInPackage.getText(), directory); + } + } + ); } final File targetFile = new File(getTargetFilePath()); @@ -324,7 +385,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { new Function0() { @Override public JetFile invoke() { - return RefactoringPackage.createKotlinFile(targetFile.getName(), psiDirectory); + return RefactoringPackage.getOrCreateKotlinFile(targetFile.getName(), psiDirectory); } } ); @@ -347,6 +408,10 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { } } + if (tfFileNameInPackage.getText().isEmpty()) { + return "File name may not be empty"; + } + return null; } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt index 84441fc4815..7880109c8e5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt @@ -156,7 +156,12 @@ fun createMoveUsageInfoIfPossible( return MoveRenameUsageInfo(element, reference, startOffset, endOffset, referencedElement, false) } -public fun JetNamedDeclaration.getFileNameAfterMove(): String = "${getName()}.${JetFileType.EXTENSION}" +public fun guessNewFileName(declarationsToMove: Collection): String? { + if (declarationsToMove.isEmpty()) return null + + val representative = declarationsToMove.firstOrNull { it is JetClassOrObject } ?: declarationsToMove.first() + return "${representative.getName()}.${JetFileType.EXTENSION}" +} // returns true if successful private fun updateJavaReference(reference: PsiReferenceExpression, oldElement: PsiElement, newElement: PsiElement): Boolean { diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/AbstractJetMoveTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/AbstractJetMoveTest.kt index 1da260d53a8..bafed43cfec 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/AbstractJetMoveTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/AbstractJetMoveTest.kt @@ -37,13 +37,11 @@ import com.intellij.refactoring.move.moveInner.MoveInnerProcessor import com.intellij.refactoring.move.moveMembers.MockMoveMembersOptions import com.intellij.refactoring.move.moveMembers.MoveMembersProcessor import com.intellij.util.ActionRunner +import org.jetbrains.kotlin.idea.core.refactoring.createKotlinFile import org.jetbrains.kotlin.idea.refactoring.move.changePackage.KotlinChangePackageRefactoring +import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.* import org.jetbrains.kotlin.idea.test.KotlinMultiFileTestCase import org.jetbrains.kotlin.idea.test.PluginTestCaseBase -import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.JetFileKotlinMoveTarget -import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.MoveDestinationKotlinMoveTarget -import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.MoveKotlinTopLevelDeclarationsOptions -import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.MoveKotlinTopLevelDeclarationsProcessor import org.jetbrains.kotlin.idea.search.allScope import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.psi.JetFile @@ -246,7 +244,10 @@ enum class MoveAction { val elementToMove = elementAtCaret!!.getNonStrictParentOfType()!! val moveTarget = config.getNullableString("targetPackage")?.let { packageName -> - MoveDestinationKotlinMoveTarget(MultipleRootsMoveDestination(PackageWrapper(mainFile.getManager(), packageName))) + DeferredJetFileKotlinMoveTarget(project, FqName(packageName)) { + val moveDestination = MultipleRootsMoveDestination(PackageWrapper(mainFile.getManager(), packageName)) + createKotlinFile(guessNewFileName(listOf(elementToMove))!!, moveDestination.getTargetDirectory(mainFile)) + } } ?: config.getString("targetFile").let { filePath -> JetFileKotlinMoveTarget(PsiManager.getInstance(project).findFile(rootDir.findFileByRelativePath(filePath)!!) as JetFile) }