Move: Allow the user to choose target file name when moving declarations to package

This commit is contained in:
Alexey Sedunov
2015-05-14 16:47:20 +03:00
parent 2bd21b0f37
commit 870c9c4668
7 changed files with 133 additions and 84 deletions
@@ -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
@@ -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<out PsiElement>, targetContainer: PsiElement?, callback: MoveCallback?
): Boolean {
fun checkNameConflicts(): Boolean {
val fileNames = HashSet<String>()
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")]
@@ -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())
@@ -26,7 +26,7 @@
<properties/>
<border type="none"/>
<children>
<grid id="fced3" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="fced3" layout-manager="GridLayoutManager" row-count="5" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="7" left="0" bottom="7" right="0"/>
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
@@ -50,13 +50,13 @@
</component>
<component id="9ea97" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="fileChooser">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
<component id="58e61" class="javax.swing.JRadioButton" binding="rbMoveToFile">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="To file:"/>
@@ -65,7 +65,7 @@
<grid id="ce694" binding="targetPanel" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
<grid row="3" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
@@ -89,7 +89,7 @@
</grid>
<component id="74471" class="com.intellij.ui.NonFocusableCheckBox" binding="cbSearchInComments">
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="3" indent="0" use-parent-layout="false"/>
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<selected value="true"/>
@@ -98,13 +98,33 @@
</component>
<component id="a7936" class="com.intellij.ui.NonFocusableCheckBox" binding="cbSearchTextOccurrences">
<constraints>
<grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="3" indent="0" use-parent-layout="false"/>
<grid row="4" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<selected value="true"/>
<text resource-bundle="messages/RefactoringBundle" key="search.for.text.occurrences"/>
</properties>
</component>
<component id="67853" class="javax.swing.JTextField" binding="tfFileNameInPackage" default-binding="true">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties>
<enabled value="false"/>
</properties>
</component>
<component id="e1add" class="javax.swing.JCheckBox" binding="cbSpecifyFileNameInPackage">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="0" indent="5" use-parent-layout="false"/>
</constraints>
<properties>
<horizontalAlignment value="2"/>
<horizontalTextPosition value="11"/>
<text value="File name:"/>
</properties>
</component>
</children>
</grid>
<vspacer id="9c908">
@@ -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<JetNamedDeclaration> elementsToMove) {
List<KotlinMemberInfo> memberInfos = KotlinPackage.map(
final List<KotlinMemberInfo> memberInfos = KotlinPackage.map(
KotlinPackage.filterIsInstance(sourceFile.getDeclarations(), JetNamedDeclaration.class),
new Function1<JetNamedDeclaration, KotlinMemberInfo>() {
@Override
@@ -160,7 +165,41 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
memberInfoModel.memberInfoChanged(new MemberInfoChange<JetNamedDeclaration, KotlinMemberInfo>(memberInfos));
selectionPanel.getTable().setMemberInfoModel(memberInfoModel);
selectionPanel.getTable().addMemberInfoChangeListener(memberInfoModel);
selectionPanel.getTable().addMemberInfoChangeListener(
new MemberInfoChangeListener<JetNamedDeclaration, KotlinMemberInfo>() {
private boolean shouldUpdateFileNameField(final Collection<KotlinMemberInfo> changedMembers) {
if (!tfFileNameInPackage.isEnabled()) return true;
Collection<JetNamedDeclaration> previousDeclarations = KotlinPackage.filterNotNull(
KotlinPackage.map(
memberInfos,
new Function1<KotlinMemberInfo, JetNamedDeclaration>() {
@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<JetNamedDeclaration, KotlinMemberInfo> 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<JetFile>() {
@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<JetFile>() {
@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;
}
@@ -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<JetNamedDeclaration>): 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 {
@@ -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<JetNamedDeclaration>()!!
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)
}