Refactor of Move refactoring

1) Expect/actual move checkbox is visible when expect/actual listed in selection list only
2) Fixed invalid temprary renaming (when source file name exists in target directory)
3) Fixed invalid file delition when "delete all files" checkbox is unchecked (KT-36072)
4) Removed UpdatePackageDirective checkbox (now it always updated instead of unpredicted behavior)
5) Removed SpecifyFileNameInPackage checkbox (in case of it redundancy)
6) Wrap resolve operations into the process
7) Minor refactorings
8) Fixed MPP declaration move top level declarations
This commit is contained in:
Igor Yakovlev
2020-02-25 23:32:41 +03:00
parent 8877559c02
commit a9de0a219b
10 changed files with 190 additions and 205 deletions
@@ -64,9 +64,7 @@ internal class MoveKotlinDeclarationsHandlerTestActions(private val caseDataKeep
"isSearchReferences = $isSearchReferences\n" + "isSearchReferences = $isSearchReferences\n" +
"isSearchInComments = $isSearchInComments\n" + "isSearchInComments = $isSearchInComments\n" +
"isSearchInNonJavaFiles = $isSearchInNonJavaFiles\n" + "isSearchInNonJavaFiles = $isSearchInNonJavaFiles\n" +
"isDeleteEmptyFiles = $isDeleteEmptyFiles\n" + "isDeleteEmptyFiles = $isDeleteEmptyFiles\n"
"isUpdatePackageDirective = $isUpdatePackageDirective\n" +
"isFullFileMove = $isFullFileMove"
} }
private fun KotlinAwareMoveFilesOrDirectoriesModel.testDataString(): String { private fun KotlinAwareMoveFilesOrDirectoriesModel.testDataString(): String {
@@ -153,10 +151,6 @@ internal class MoveKotlinDeclarationsHandlerTestActions(private val caseDataKeep
targetDirectory: PsiDirectory?, targetDirectory: PsiDirectory?,
targetFile: KtFile?, targetFile: KtFile?,
moveToPackage: Boolean, moveToPackage: Boolean,
searchInComments: Boolean,
searchForTextOccurrences: Boolean,
deleteEmptySourceFiles: Boolean,
moveMppDeclarations: Boolean,
moveCallback: MoveCallback? moveCallback: MoveCallback?
) { ) {
val selectedElementsToMove = mutableSetOf<KtNamedDeclaration>() val selectedElementsToMove = mutableSetOf<KtNamedDeclaration>()
@@ -180,8 +174,6 @@ internal class MoveKotlinDeclarationsHandlerTestActions(private val caseDataKeep
isSearchInComments = randomBoolean(), isSearchInComments = randomBoolean(),
isSearchInNonJavaFiles = randomBoolean(), isSearchInNonJavaFiles = randomBoolean(),
isDeleteEmptyFiles = randomBoolean(), isDeleteEmptyFiles = randomBoolean(),
isUpdatePackageDirective = randomBoolean(),
isFullFileMove = randomBoolean(),
applyMPPDeclarations = true, applyMPPDeclarations = true,
moveCallback = null moveCallback = null
) )
@@ -68,10 +68,6 @@ private val defaultHandlerActions = object : MoveKotlinDeclarationsHandlerAction
targetDirectory: PsiDirectory?, targetDirectory: PsiDirectory?,
targetFile: KtFile?, targetFile: KtFile?,
moveToPackage: Boolean, moveToPackage: Boolean,
searchInComments: Boolean,
searchForTextOccurrences: Boolean,
deleteEmptySourceFiles: Boolean,
moveMppDeclarations: Boolean,
moveCallback: MoveCallback? moveCallback: MoveCallback?
) = MoveKotlinTopLevelDeclarationsDialog( ) = MoveKotlinTopLevelDeclarationsDialog(
project, project,
@@ -80,10 +76,6 @@ private val defaultHandlerActions = object : MoveKotlinDeclarationsHandlerAction
targetDirectory, targetDirectory,
targetFile, targetFile,
moveToPackage, moveToPackage,
searchInComments,
searchForTextOccurrences,
deleteEmptySourceFiles,
moveMppDeclarations,
moveCallback moveCallback
).show() ).show()
@@ -180,6 +172,15 @@ class MoveKotlinDeclarationsHandler internal constructor(private val handlerActi
} }
val initialTargetDirectory = MoveFilesOrDirectoriesUtil.resolveToDirectory(project, initialTargetElement) val initialTargetDirectory = MoveFilesOrDirectoriesUtil.resolveToDirectory(project, initialTargetElement)
if (!ApplicationManager.getApplication().isUnitTestMode && elementsToSearch.any { it.isExpectDeclaration() || it.isEffectivelyActual() }
) {
val message = RefactoringBundle.getCannotRefactorMessage(
"This refactoring will move selected declaration without it's expect/actual counterparts that may lead to compilation errors.\n\"Do you wish to proceed?\"")
val title = RefactoringBundle.getCannotRefactorMessage("MPP declarations does not supported by this refactoring.")
val proceedWithIncompleteRefactoring = Messages.showYesNoDialog(project, message, title, Messages.getWarningIcon())
if (proceedWithIncompleteRefactoring != Messages.YES) return true
}
handlerActions.invokeKotlinAwareMoveFilesOrDirectoriesRefactoring( handlerActions.invokeKotlinAwareMoveFilesOrDirectoriesRefactoring(
project, initialTargetDirectory, ktFileElements, callback project, initialTargetDirectory, ktFileElements, callback
) )
@@ -193,10 +194,6 @@ class MoveKotlinDeclarationsHandler internal constructor(private val handlerActi
val targetDirectory = if (targetContainer != null) { val targetDirectory = if (targetContainer != null) {
MoveClassesOrPackagesImpl.getInitialTargetDirectory(targetContainer, elements) MoveClassesOrPackagesImpl.getInitialTargetDirectory(targetContainer, elements)
} else null } else null
val searchInComments = KotlinRefactoringSettings.instance.MOVE_SEARCH_IN_COMMENTS
val searchInText = KotlinRefactoringSettings.instance.MOVE_SEARCH_FOR_TEXT
val deleteEmptySourceFiles = KotlinRefactoringSettings.instance.MOVE_DELETE_EMPTY_SOURCE_FILES
val moveMppDeclarations = KotlinRefactoringSettings.instance.MOVE_MPP_DECLARATIONS
val targetFile = targetContainer as? KtFile val targetFile = targetContainer as? KtFile
val moveToPackage = targetContainer !is KtFile val moveToPackage = targetContainer !is KtFile
@@ -207,10 +204,6 @@ class MoveKotlinDeclarationsHandler internal constructor(private val handlerActi
targetDirectory, targetDirectory,
targetFile, targetFile,
moveToPackage, moveToPackage,
searchInComments,
searchInText,
deleteEmptySourceFiles,
moveMppDeclarations,
callback callback
) )
} }
@@ -31,10 +31,6 @@ internal interface MoveKotlinDeclarationsHandlerActions {
targetDirectory: PsiDirectory?, targetDirectory: PsiDirectory?,
targetFile: KtFile?, targetFile: KtFile?,
moveToPackage: Boolean, moveToPackage: Boolean,
searchInComments: Boolean,
searchForTextOccurrences: Boolean,
deleteEmptySourceFiles: Boolean,
moveMppDeclarations: Boolean,
moveCallback: MoveCallback? moveCallback: MoveCallback?
) )
@@ -363,7 +363,7 @@ class MoveKotlinDeclarationsProcessor(
} }
} }
if (descriptor.deleteSourceFiles) { if (descriptor.deleteSourceFiles && sourceFile.declarations.isEmpty()) {
sourceFile.delete() sourceFile.delete()
} }
} }
@@ -66,22 +66,19 @@ class MoveToKotlinFileProcessor @JvmOverloads constructor(
return super.showConflicts(conflicts, usages) return super.showConflicts(conflicts, usages)
} }
// Assign a temporary name to file-under-move to avoid naming conflict during the refactoring private fun renameFileTemporarilyIfNeeded() {
private fun renameFileTemporarily() { if (targetDirectory.findFile(sourceFile.name) == null) return
if (targetDirectory.findFile(targetFileName) == null) return
val containingDirectory = sourceFile.containingDirectory ?: return
val temporaryName = UniqueNameGenerator.generateUniqueName("temp", "", ".kt") { val temporaryName = UniqueNameGenerator.generateUniqueName("temp", "", ".kt") {
sourceFile.containingDirectory!!.findFile(it) == null containingDirectory.findFile(it) == null
} }
sourceFile.name = temporaryName sourceFile.name = temporaryName
} }
override fun performRefactoring(usages: Array<UsageInfo>) { override fun performRefactoring(usages: Array<UsageInfo>) {
val needTemporaryRename = targetDirectory.findFile(sourceFile.name) != null renameFileTemporarilyIfNeeded()
if (needTemporaryRename) {
renameFileTemporarily()
}
super.performRefactoring(usages) super.performRefactoring(usages)
sourceFile.name = targetFileName sourceFile.name = targetFileName
} }
@@ -129,9 +129,11 @@ internal abstract class MoveKotlinNestedClassesToUpperLevelModel(
if (targetContainer is KtClassOrObject) { if (targetContainer is KtClassOrObject) {
val targetClass = targetContainer as KtClassOrObject? val targetClass = targetContainer as KtClassOrObject?
for (member in targetClass!!.declarations) { if (targetClass != null) {
if (member is KtClassOrObject && className == member.getName()) { for (member in targetClass.declarations) {
throw ConfigurationException(RefactoringBundle.message("inner.class.exists", className, targetClass.name)) if (member is KtClassOrObject && className == member.getName()) {
throw ConfigurationException(RefactoringBundle.message("inner.class.exists", className, targetClass.name))
}
} }
} }
} }
@@ -62,6 +62,15 @@
<text resource-bundle="messages/KotlinBundle" key="label.text.to.file"/> <text resource-bundle="messages/KotlinBundle" key="label.text.to.file"/>
</properties> </properties>
</component> </component>
<component id="4934c" class="javax.swing.JLabel">
<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>
<labelFor value="67853"/>
<text resource-bundle="messages/KotlinBundle" key="label.text.file.name"/>
</properties>
</component>
<component id="67853" class="javax.swing.JTextField" binding="tfFileNameInPackage" default-binding="true"> <component id="67853" class="javax.swing.JTextField" binding="tfFileNameInPackage" default-binding="true">
<constraints> <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"> <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">
@@ -72,16 +81,6 @@
<enabled value="false"/> <enabled value="false"/>
</properties> </properties>
</component> </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 resource-bundle="messages/KotlinBundle" key="label.text.file.name"/>
</properties>
</component>
<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"> <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"/> <margin top="0" left="0" bottom="0" right="0"/>
<constraints> <constraints>
@@ -107,15 +106,6 @@
</component> </component>
</children> </children>
</grid> </grid>
<component id="684be" class="javax.swing.JCheckBox" binding="cbUpdatePackageDirective">
<constraints>
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="5" use-parent-layout="false"/>
</constraints>
<properties>
<selected value="true"/>
<text resource-bundle="messages/KotlinBundle" key="label.text.update.package.directive"/>
</properties>
</component>
<grid id="69aba" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> <grid id="69aba" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/> <margin top="0" left="0" bottom="0" right="0"/>
<constraints> <constraints>
@@ -16,7 +16,6 @@ import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.classMembers.AbstractMemberInfoModel; import com.intellij.refactoring.classMembers.AbstractMemberInfoModel;
import com.intellij.refactoring.classMembers.MemberInfoBase; import com.intellij.refactoring.classMembers.MemberInfoBase;
import com.intellij.refactoring.classMembers.MemberInfoChange; import com.intellij.refactoring.classMembers.MemberInfoChange;
import com.intellij.refactoring.classMembers.MemberInfoChangeListener;
import com.intellij.refactoring.move.MoveCallback; import com.intellij.refactoring.move.MoveCallback;
import com.intellij.refactoring.move.MoveHandler; import com.intellij.refactoring.move.MoveHandler;
import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo; import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo;
@@ -31,7 +30,6 @@ import kotlin.collections.CollectionsKt;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.KotlinBundle; import org.jetbrains.kotlin.idea.KotlinBundle;
import org.jetbrains.kotlin.idea.core.PackageUtilsKt;
import org.jetbrains.kotlin.idea.core.util.PhysicalFileSystemUtilsKt; import org.jetbrains.kotlin.idea.core.util.PhysicalFileSystemUtilsKt;
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringSettings; import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringSettings;
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo; import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo;
@@ -49,8 +47,10 @@ import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.io.File; import java.io.File;
import java.util.*; import java.util.BitSet;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set;
import static org.jetbrains.kotlin.idea.roots.ProjectRootUtilsKt.getSuitableDestinationSourceRoots; import static org.jetbrains.kotlin.idea.roots.ProjectRootUtilsKt.getSuitableDestinationSourceRoots;
@@ -69,44 +69,31 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
private TextFieldWithBrowseButton fileChooser; private TextFieldWithBrowseButton fileChooser;
private JPanel memberInfoPanel; private JPanel memberInfoPanel;
private JTextField tfFileNameInPackage; private JTextField tfFileNameInPackage;
private JCheckBox cbSpecifyFileNameInPackage;
private JCheckBox cbUpdatePackageDirective;
private JCheckBox cbDeleteEmptySourceFiles; private JCheckBox cbDeleteEmptySourceFiles;
private JCheckBox cbSearchReferences; private JCheckBox cbSearchReferences;
private JCheckBox cbApplyMPPDeclarationsMove; private JCheckBox cbApplyMPPDeclarationsMove;
private KotlinMemberSelectionTable memberTable; private KotlinMemberSelectionTable memberTable;
private class MemberSelectionerInfoChangeListener implements MemberInfoChangeListener<KtNamedDeclaration, KotlinMemberInfo> { public MoveKotlinTopLevelDeclarationsDialog(
@NotNull Project project,
private final List<KotlinMemberInfo> memberInfos; @NotNull Set<KtNamedDeclaration> elementsToMove,
@Nullable String targetPackageName,
public MemberSelectionerInfoChangeListener(List<KotlinMemberInfo> memberInfos) { @Nullable PsiDirectory targetDirectory,
this.memberInfos = memberInfos; @Nullable KtFile targetFile,
} boolean moveToPackage,
@Nullable MoveCallback moveCallback
private boolean shouldUpdateFileNameField(Collection<KotlinMemberInfo> changedMembers) { ) {
if (!tfFileNameInPackage.isEnabled()) return true; this(project,
elementsToMove,
Collection<KtNamedDeclaration> previousDeclarations = CollectionsKt.filterNotNull( targetPackageName,
CollectionsKt.map( targetDirectory,
memberInfos, targetFile,
info -> changedMembers.contains(info) != info.isChecked() ? info.getMember() : null moveToPackage,
) KotlinRefactoringSettings.getInstance().MOVE_SEARCH_IN_COMMENTS,
); KotlinRefactoringSettings.getInstance().MOVE_SEARCH_FOR_TEXT,
String suggestedText = previousDeclarations.isEmpty() ? "" : MoveUtilsKt.guessNewFileName(previousDeclarations); KotlinRefactoringSettings.getInstance().MOVE_DELETE_EMPTY_SOURCE_FILES,
return tfFileNameInPackage.getText().equals(suggestedText); KotlinRefactoringSettings.getInstance().MOVE_MPP_DECLARATIONS,
} moveCallback);
@Override
public void memberInfoChanged(@NotNull MemberInfoChange<KtNamedDeclaration, KotlinMemberInfo> event) {
updateControls();
updatePackageDirectiveCheckBox();
updateFileNameInPackageField();
// Update file name field only if it user hasn't changed it to some non-default value
if (shouldUpdateFileNameField(event.getChangedMembers())) {
updateSuggestedFileName();
}
}
} }
public MoveKotlinTopLevelDeclarationsDialog( public MoveKotlinTopLevelDeclarationsDialog(
@@ -124,16 +111,19 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
) { ) {
super(project, true); super(project, true);
init();
List<KtFile> sourceFiles = getSourceFiles(elementsToMove); List<KtFile> sourceFiles = getSourceFiles(elementsToMove);
this.moveCallback = moveCallback; this.moveCallback = moveCallback;
this.initialTargetDirectory = targetDirectory; this.initialTargetDirectory = targetDirectory;
init();
setTitle(MoveHandler.REFACTORING_NAME); setTitle(MoveHandler.REFACTORING_NAME);
initSearchOptions(searchInComments, searchForTextOccurrences, deleteEmptySourceFiles, moveMppDeclarations); List<KtNamedDeclaration> allDeclarations = getAllDeclarations(sourceFiles);
initSearchOptions(searchInComments, searchForTextOccurrences, deleteEmptySourceFiles, moveMppDeclarations, allDeclarations);
initPackageChooser(targetPackageName, targetDirectory, sourceFiles); initPackageChooser(targetPackageName, targetDirectory, sourceFiles);
@@ -141,25 +131,28 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
initMoveToButtons(moveToPackage); initMoveToButtons(moveToPackage);
initMemberInfo(elementsToMove, sourceFiles); initMemberInfo(elementsToMove, allDeclarations);
updateControls(); updateControls();
initializedCheckBoxesState = getCheckboxesState(true); initializedCheckBoxesState = getCheckboxesState(true);
} }
@Override
protected void init() {
super.init();
}
private final BitSet initializedCheckBoxesState; private final BitSet initializedCheckBoxesState;
private BitSet getCheckboxesState(boolean applyDefaults) { private BitSet getCheckboxesState(boolean applyDefaults) {
BitSet state = new BitSet(7); BitSet state = new BitSet(5);
state.set(0, applyDefaults || cbSearchInComments.isSelected()); //cbSearchInComments default is true state.set(0, applyDefaults || cbSearchInComments.isSelected()); //cbSearchInComments default is true
state.set(1, applyDefaults || cbSearchTextOccurrences.isSelected()); //cbSearchTextOccurrences default is true state.set(1, applyDefaults || cbSearchTextOccurrences.isSelected()); //cbSearchTextOccurrences default is true
state.set(2, applyDefaults || cbDeleteEmptySourceFiles.isSelected()); //cbDeleteEmptySourceFiles default is true state.set(2, applyDefaults || cbDeleteEmptySourceFiles.isSelected()); //cbDeleteEmptySourceFiles default is true
state.set(3, applyDefaults || cbApplyMPPDeclarationsMove.isSelected()); //cbApplyMPPDeclarationsMove default is true state.set(3, applyDefaults || cbApplyMPPDeclarationsMove.isSelected()); //cbApplyMPPDeclarationsMove default is true
state.set(4, cbSpecifyFileNameInPackage.isSelected()); state.set(4, cbSearchReferences.isSelected());
state.set(5, cbUpdatePackageDirective.isSelected());
state.set(6, cbSearchReferences.isSelected());
return state; return state;
} }
@@ -183,32 +176,24 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
); );
} }
private static boolean arePackagesAndDirectoryMatched(List<KtFile> sourceFiles) {
for (KtFile sourceFile : sourceFiles) {
if (!PackageUtilsKt.packageMatchesDirectoryOrImplicit(sourceFile)) return false;
}
return true;
}
private void initMemberInfo( private void initMemberInfo(
@NotNull Set<KtNamedDeclaration> elementsToMove, @NotNull Set<KtNamedDeclaration> elementsToMove,
@NotNull List<KtFile> sourceFiles @NotNull List<KtNamedDeclaration> declarations
) { ) {
List<KotlinMemberInfo> memberInfos = CollectionsKt.map( ////KotlinMemberInfo run resolve on declaration so it is good to place it to the process
getAllDeclarations(sourceFiles), List<KotlinMemberInfo> memberInfos = MoveUtilsKt.mapWithReadActionInProcess(declarations, myProject, MoveHandler.REFACTORING_NAME, (declaration) -> {
declaration -> { KotlinMemberInfo memberInfo = new KotlinMemberInfo(declaration, false);
KotlinMemberInfo memberInfo = new KotlinMemberInfo(declaration, false); memberInfo.setChecked(elementsToMove.contains(declaration));
memberInfo.setChecked(elementsToMove.contains(declaration)); return memberInfo;
return memberInfo; });
}
);
KotlinMemberSelectionPanel selectionPanel = new KotlinMemberSelectionPanel(getTitle(), memberInfos, null); KotlinMemberSelectionPanel selectionPanel = new KotlinMemberSelectionPanel(getTitle(), memberInfos, null);
memberTable = selectionPanel.getTable(); memberTable = selectionPanel.getTable();
MemberInfoModelImpl memberInfoModel = new MemberInfoModelImpl(); MemberInfoModelImpl memberInfoModel = new MemberInfoModelImpl();
memberInfoModel.memberInfoChanged(new MemberInfoChange<>(memberInfos)); memberInfoModel.memberInfoChanged(new MemberInfoChange<>(memberInfos));
selectionPanel.getTable().setMemberInfoModel(memberInfoModel); selectionPanel.getTable().setMemberInfoModel(memberInfoModel);
selectionPanel.getTable().addMemberInfoChangeListener(memberInfoModel); selectionPanel.getTable().addMemberInfoChangeListener(memberInfoModel);
selectionPanel.getTable().addMemberInfoChangeListener(new MemberSelectionerInfoChangeListener(memberInfos)); selectionPanel.getTable().addMemberInfoChangeListener(listener -> updateControls());
cbApplyMPPDeclarationsMove.addChangeListener(e -> updateControls()); cbApplyMPPDeclarationsMove.addChangeListener(e -> updateControls());
memberInfoPanel.add(selectionPanel, BorderLayout.CENTER); memberInfoPanel.add(selectionPanel, BorderLayout.CENTER);
} }
@@ -219,8 +204,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
private void updateFileNameInPackageField() { private void updateFileNameInPackageField() {
boolean movingSingleFileToPackage = rbMoveToPackage.isSelected() && getSourceFiles(getSelectedElementsToMove()).size() == 1; boolean movingSingleFileToPackage = rbMoveToPackage.isSelected() && getSourceFiles(getSelectedElementsToMove()).size() == 1;
cbSpecifyFileNameInPackage.setEnabled(movingSingleFileToPackage); tfFileNameInPackage.setEnabled(movingSingleFileToPackage);
tfFileNameInPackage.setEnabled(movingSingleFileToPackage && cbSpecifyFileNameInPackage.isSelected());
} }
private void initPackageChooser( private void initPackageChooser(
@@ -243,17 +227,20 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
}, },
classPackageChooser.getChildComponent() classPackageChooser.getChildComponent()
); );
cbSpecifyFileNameInPackage.addActionListener(e -> updateFileNameInPackageField());
cbUpdatePackageDirective.setSelected(arePackagesAndDirectoryMatched(sourceFiles));
} }
private void initSearchOptions(boolean searchInComments, boolean searchForTextOccurences, boolean deleteEmptySourceFiles, boolean moveMppDeclarations) { private void initSearchOptions(
boolean searchInComments,
boolean searchForTextOccurences,
boolean deleteEmptySourceFiles,
boolean moveMppDeclarations,
List<KtNamedDeclaration> allDeclarations
) {
cbSearchInComments.setSelected(searchInComments); cbSearchInComments.setSelected(searchInComments);
cbSearchTextOccurrences.setSelected(searchForTextOccurences); cbSearchTextOccurrences.setSelected(searchForTextOccurences);
cbDeleteEmptySourceFiles.setSelected(deleteEmptySourceFiles); cbDeleteEmptySourceFiles.setSelected(deleteEmptySourceFiles);
cbApplyMPPDeclarationsMove.setSelected(moveMppDeclarations); cbApplyMPPDeclarationsMove.setSelected(moveMppDeclarations);
cbApplyMPPDeclarationsMove.setVisible(isMPPDeclarationInList(allDeclarations));
} }
private void initMoveToButtons(boolean moveToPackage) { private void initMoveToButtons(boolean moveToPackage) {
@@ -348,8 +335,8 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
); );
} }
private boolean isMppDeclarationSelected() { private boolean isMPPDeclarationInList(List<KtNamedDeclaration> declarations) {
for(KtNamedDeclaration element : getSelectedElementsToMove()) { for(KtNamedDeclaration element : declarations) {
if (ExpectActualUtilKt.isEffectivelyActual(element, true) || if (ExpectActualUtilKt.isEffectivelyActual(element, true) ||
ExpectActualUtilKt.isExpectDeclaration(element)) { ExpectActualUtilKt.isExpectDeclaration(element)) {
return true; return true;
@@ -358,6 +345,10 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
return false; return false;
} }
private boolean isMppDeclarationSelected() {
return isMPPDeclarationInList(getSelectedElementsToMove());
}
private void updateControls() { private void updateControls() {
boolean mppDeclarationSelected = isMppDeclarationSelected(); boolean mppDeclarationSelected = isMppDeclarationSelected();
@@ -376,27 +367,11 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
classPackageChooser.setEnabled(moveToPackage); classPackageChooser.setEnabled(moveToPackage);
updateFileNameInPackageField(); updateFileNameInPackageField();
fileChooser.setEnabled(!moveToPackage); fileChooser.setEnabled(!moveToPackage);
updatePackageDirectiveCheckBox(); UIUtil.setEnabled(targetPanel, moveToPackage && !needToMoveMPPDeclarations && hasAnySourceRoots(), true);
UIUtil.setEnabled(targetPanel, moveToPackage && hasAnySourceRoots(), true);
updateSuggestedFileName(); updateSuggestedFileName();
myHelpAction.setEnabled(false); myHelpAction.setEnabled(false);
} }
private boolean isFullFileMove() {
Map<KtFile, List<KtNamedDeclaration>> fileToElements = CollectionsKt.groupBy(
getSelectedElementsToMove(),
KtPureElement::getContainingKtFile
);
for (Map.Entry<KtFile, List<KtNamedDeclaration>> entry : fileToElements.entrySet()) {
if (KtPsiUtilKt.getFileOrScriptDeclarations(entry.getKey()).size() != entry.getValue().size()) return false;
}
return true;
}
private void updatePackageDirectiveCheckBox() {
cbUpdatePackageDirective.setEnabled(rbMoveToPackage.isSelected() && isFullFileMove());
}
private boolean hasAnySourceRoots() { private boolean hasAnySourceRoots() {
return !getSuitableDestinationSourceRoots(myProject).isEmpty(); return !getSuitableDestinationSourceRoots(myProject).isEmpty();
} }
@@ -443,11 +418,14 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
private MoveKotlinTopLevelDeclarationsModel getModel() throws ConfigurationException { private MoveKotlinTopLevelDeclarationsModel getModel() throws ConfigurationException {
DirectoryChooser.ItemWrapper selectedItem = (DirectoryChooser.ItemWrapper) destinationFolderCB.getComboBox().getSelectedItem();
PsiDirectory selectedPsiDirectory = selectedItem != null ? selectedItem.getDirectory() : initialTargetDirectory;
boolean mppDeclarationSelected = cbApplyMPPDeclarationsMove.isSelected() && isMppDeclarationSelected(); boolean mppDeclarationSelected = cbApplyMPPDeclarationsMove.isSelected() && isMppDeclarationSelected();
PsiDirectory selectedPsiDirectory = null;
if (!mppDeclarationSelected) {
DirectoryChooser.ItemWrapper selectedItem = (DirectoryChooser.ItemWrapper) destinationFolderCB.getComboBox().getSelectedItem();
selectedPsiDirectory = selectedItem != null ? selectedItem.getDirectory() : initialTargetDirectory;
}
List<KtNamedDeclaration> selectedElements = getSelectedElementsToMoveChecked(); List<KtNamedDeclaration> selectedElements = getSelectedElementsToMoveChecked();
return new MoveKotlinTopLevelDeclarationsModel( return new MoveKotlinTopLevelDeclarationsModel(
@@ -462,8 +440,6 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
cbSearchInComments.isSelected(), cbSearchInComments.isSelected(),
cbSearchTextOccurrences.isSelected(), cbSearchTextOccurrences.isSelected(),
cbDeleteEmptySourceFiles.isSelected(), cbDeleteEmptySourceFiles.isSelected(),
cbUpdatePackageDirective.isSelected(),
isFullFileMove(),
mppDeclarationSelected, mppDeclarationSelected,
moveCallback moveCallback
); );
@@ -16,6 +16,7 @@ import com.intellij.refactoring.BaseRefactoringProcessor
import com.intellij.refactoring.MoveDestination import com.intellij.refactoring.MoveDestination
import com.intellij.refactoring.PackageWrapper import com.intellij.refactoring.PackageWrapper
import com.intellij.refactoring.move.MoveCallback import com.intellij.refactoring.move.MoveCallback
import com.intellij.refactoring.move.MoveHandler
import com.intellij.refactoring.move.moveClassesOrPackages.AutocreatingSingleSourceRootMoveDestination import com.intellij.refactoring.move.moveClassesOrPackages.AutocreatingSingleSourceRootMoveDestination
import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDestination import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDestination
import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.KotlinBundle
@@ -25,18 +26,17 @@ import org.jetbrains.kotlin.idea.core.util.toPsiDirectory
import org.jetbrains.kotlin.idea.core.util.toPsiFile import org.jetbrains.kotlin.idea.core.util.toPsiFile
import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile
import org.jetbrains.kotlin.idea.refactoring.move.getOrCreateDirectory import org.jetbrains.kotlin.idea.refactoring.move.getOrCreateDirectory
import org.jetbrains.kotlin.idea.refactoring.move.mapWithReadActionInProcess
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.* import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*
import org.jetbrains.kotlin.idea.refactoring.move.updatePackageDirective import org.jetbrains.kotlin.idea.refactoring.move.updatePackageDirective
import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector.MovedEntity
import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector.MoveRefactoringDestination import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector.MoveRefactoringDestination
import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector.MovedEntity
import org.jetbrains.kotlin.idea.util.collectAllExpectAndActualDeclaration import org.jetbrains.kotlin.idea.util.collectAllExpectAndActualDeclaration
import org.jetbrains.kotlin.idea.util.isEffectivelyActual import org.jetbrains.kotlin.idea.util.isEffectivelyActual
import org.jetbrains.kotlin.idea.util.isExpectDeclaration import org.jetbrains.kotlin.idea.util.isExpectDeclaration
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.psiUtil.getFileOrScriptDeclarations
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import java.io.File import java.io.File
import java.nio.file.InvalidPathException import java.nio.file.InvalidPathException
import java.nio.file.Paths import java.nio.file.Paths
@@ -53,8 +53,6 @@ internal class MoveKotlinTopLevelDeclarationsModel(
val isSearchInComments: Boolean, val isSearchInComments: Boolean,
val isSearchInNonJavaFiles: Boolean, val isSearchInNonJavaFiles: Boolean,
val isDeleteEmptyFiles: Boolean, val isDeleteEmptyFiles: Boolean,
val isUpdatePackageDirective: Boolean,
val isFullFileMove: Boolean,
val applyMPPDeclarations: Boolean, val applyMPPDeclarations: Boolean,
val moveCallback: MoveCallback? val moveCallback: MoveCallback?
) : Model { ) : Model {
@@ -65,9 +63,13 @@ internal class MoveKotlinTopLevelDeclarationsModel(
private fun checkedGetSourceDirectory() = sourceFiles.mapToSingleOrNull { it.parent } private fun checkedGetSourceDirectory() = sourceFiles.mapToSingleOrNull { it.parent }
?: throw ConfigurationException(KotlinBundle.message("text.cannot.determine.source.directory")) ?: throw ConfigurationException(KotlinBundle.message("text.cannot.determine.source.directory"))
private val sourceFiles: Set<KtFile> = elementsToMove.mapTo(mutableSetOf()) { it.containingKtFile } private val sourceFiles: Set<KtFile> by lazy {
elementsToMove.mapTo(mutableSetOf()) { it.containingKtFile }
}
private val elementsToMoveHasMPP = applyMPPDeclarations && elementsToMove.any { it.isEffectivelyActual() || it.isExpectDeclaration() } private val elementsToMoveHasMPP by lazy {
applyMPPDeclarations && elementsToMove.any { it.isEffectivelyActual() || it.isExpectDeclaration() }
}
private val singleSourceFileMode = sourceFiles.size == 1 private val singleSourceFileMode = sourceFiles.size == 1
@@ -121,15 +123,17 @@ internal class MoveKotlinTopLevelDeclarationsModel(
} }
private fun selectMoveTargetToPackage(): KotlinMoveTarget { private fun selectMoveTargetToPackage(): KotlinMoveTarget {
require(sourceFiles.isNotEmpty())
val moveDestination = selectPackageBasedDestination() val moveDestination = selectPackageBasedDestination()
val targetDirectory: PsiDirectory? = moveDestination.getTargetIfExists(checkedGetSourceDirectory())
val targetFileName = if (singleSourceFileMode) fileNameInPackage.also(::checkTargetFileName) else null val targetDirectory: PsiDirectory?
if (!elementsToMoveHasMPP) {
if (targetDirectory != null) { targetDirectory = moveDestination.getTargetIfExists(checkedGetSourceDirectory())
tryMoveToPackageForExistingDirectory(targetFileName, targetDirectory)?.let { return it } val targetFileName = if (singleSourceFileMode) fileNameInPackage.also(::checkTargetFileName) else null
if (targetDirectory != null) {
tryMoveToPackageForExistingDirectory(targetFileName, targetDirectory)?.let { return it }
}
} else {
targetDirectory = null
} }
return KotlinMoveTargetForDeferredFile( return KotlinMoveTargetForDeferredFile(
@@ -193,9 +197,6 @@ internal class MoveKotlinTopLevelDeclarationsModel(
} }
} }
private fun selectMoveTarget() =
if (isMoveToPackage) selectMoveTargetToPackage() else selectMoveTargetToFile()
private fun verifyBeforeRun() { private fun verifyBeforeRun() {
if (!isMoveToPackage && elementsToMoveHasMPP) if (!isMoveToPackage && elementsToMoveHasMPP)
throw ConfigurationException(KotlinBundle.message("text.cannot.move.expect.actual.declaration.to.file")) throw ConfigurationException(KotlinBundle.message("text.cannot.move.expect.actual.declaration.to.file"))
@@ -217,14 +218,7 @@ internal class MoveKotlinTopLevelDeclarationsModel(
} }
} }
@Throws(ConfigurationException::class) private fun getFUSParameters(): Pair<MovedEntity, MoveRefactoringDestination> {
override fun computeModelResult() = computeModelResult(throwOnConflicts = false)
@Throws(ConfigurationException::class)
override fun computeModelResult(throwOnConflicts: Boolean): ModelResultWithFUSData {
verifyBeforeRun()
val classType = if (elementsToMoveHasMPP) MovedEntity.MPPCLASSES else MovedEntity.CLASSES val classType = if (elementsToMoveHasMPP) MovedEntity.MPPCLASSES else MovedEntity.CLASSES
val functionType = if (elementsToMoveHasMPP) MovedEntity.MPPFUNCTIONS else MovedEntity.FUNCTIONS val functionType = if (elementsToMoveHasMPP) MovedEntity.MPPFUNCTIONS else MovedEntity.FUNCTIONS
val mixedType = if (elementsToMoveHasMPP) MovedEntity.MPPMIXED else MovedEntity.MIXED val mixedType = if (elementsToMoveHasMPP) MovedEntity.MPPMIXED else MovedEntity.MIXED
@@ -236,41 +230,58 @@ internal class MoveKotlinTopLevelDeclarationsModel(
val destination = if (isMoveToPackage) MoveRefactoringDestination.PACKAGE else MoveRefactoringDestination.FILE val destination = if (isMoveToPackage) MoveRefactoringDestination.PACKAGE else MoveRefactoringDestination.FILE
if (isFullFileMove && isMoveToPackage) { return entity to destination
tryMoveFile(throwOnConflicts)?.let { }
return ModelResultWithFUSData(it, elementsToMove.size, entity, destination)
}
} @Throws(ConfigurationException::class)
override fun computeModelResult() = computeModelResult(throwOnConflicts = false)
@Throws(ConfigurationException::class)
override fun computeModelResult(throwOnConflicts: Boolean): ModelResultWithFUSData {
verifyBeforeRun()
val (entity, destination) = getFUSParameters()
val processor = tryMoveEntireFile(throwOnConflicts) ?: moveDeclaration(throwOnConflicts)
val processor = moveDeclaration(throwOnConflicts)
return ModelResultWithFUSData(processor, elementsToMove.size, entity, destination) return ModelResultWithFUSData(processor, elementsToMove.size, entity, destination)
} }
private fun tryMoveFile(throwOnConflicts: Boolean): BaseRefactoringProcessor? { private fun tryMoveEntireFile(throwOnConflicts: Boolean): BaseRefactoringProcessor? {
if (elementsToMoveHasMPP) return null if (!isDeleteEmptyFiles || elementsToMoveHasMPP || !isMoveToPackage) return null
val targetFileName = if (sourceFiles.size > 1) null else fileNameInPackage val allDeclarationsMovingOut = elementsToMove
if (targetFileName != null) checkTargetFileName(targetFileName) .groupBy { obj: KtPureElement -> obj.containingKtFile }
.all { it.key.getFileOrScriptDeclarations().size == it.value.size }
if (!allDeclarationsMovingOut) return null
val moveDestination = selectPackageBasedDestination() val targetDirectory = selectPackageBasedDestination()
val targetDirectory = moveDestination.getTargetIfExists(checkedGetSourceDirectory()) ?: return null .getTargetIfExists(checkedGetSourceDirectory())
?: return null
val filesExistingInTargetDir = getFilesExistingInTargetDirectory(targetFileName, targetDirectory) val targetFileNameAndFile = sourceFiles
.singleOrNull()
?.let { fileNameInPackage to it }
?.also { checkTargetFileName(it.first) }
val filesExistingInTargetDir = getFilesExistingInTargetDirectory(targetFileNameAndFile?.first, targetDirectory)
val moveAsFile = filesExistingInTargetDir.isEmpty() || val moveAsFile = filesExistingInTargetDir.isEmpty() ||
filesExistingInTargetDir.singleOrNull()?.let { sourceFiles.contains(it) } == true filesExistingInTargetDir.singleOrNull()?.let { sourceFiles.contains(it) } == true
if (!moveAsFile) return null if (!moveAsFile) return null
sourceFiles.forEach { it.updatePackageDirective = isUpdatePackageDirective } sourceFiles.forEach { it.updatePackageDirective = true }
return if (targetFileName != null) return if (targetFileNameAndFile != null)
MoveToKotlinFileProcessor( MoveToKotlinFileProcessor(
project, project,
sourceFiles.first(), targetFileNameAndFile.second,
targetDirectory, targetDirectory,
targetFileName, targetFileNameAndFile.first,
searchInComments = isSearchInComments, searchInComments = isSearchInComments,
searchInNonJavaFiles = isSearchInNonJavaFiles, searchInNonJavaFiles = isSearchInNonJavaFiles,
moveCallback = moveCallback, moveCallback = moveCallback,
@@ -291,13 +302,16 @@ internal class MoveKotlinTopLevelDeclarationsModel(
private fun moveDeclaration(throwOnConflicts: Boolean): BaseRefactoringProcessor { private fun moveDeclaration(throwOnConflicts: Boolean): BaseRefactoringProcessor {
val elementsWithMPPIfNeeded = if (elementsToMoveHasMPP) require(isMoveToPackage && selectedPsiDirectory == null)
if (elementsToMoveHasMPP) elementsToMove
.flatMap { it.collectAllExpectAndActualDeclaration() } val target = if (isMoveToPackage) selectMoveTargetToPackage() else selectMoveTargetToFile()
.filterIsInstance<KtNamedDeclaration>()
else elementsToMove val elementsWithMPPIfNeeded = if (elementsToMoveHasMPP)
elementsToMove.mapWithReadActionInProcess(project, MoveHandler.REFACTORING_NAME) {
it.collectAllExpectAndActualDeclaration()
}.flatten().filterIsInstance<KtNamedDeclaration>()
else elementsToMove
val target = selectMoveTarget()
for (element in elementsWithMPPIfNeeded) { for (element in elementsWithMPPIfNeeded) {
target.verify(element.containingFile)?.let { throw ConfigurationException(it) } target.verify(element.containingFile)?.let { throw ConfigurationException(it) }
} }
@@ -309,7 +323,7 @@ internal class MoveKotlinTopLevelDeclarationsModel(
MoveDeclarationsDelegate.TopLevel, MoveDeclarationsDelegate.TopLevel,
isSearchInComments, isSearchInComments,
isSearchInNonJavaFiles, isSearchInNonJavaFiles,
deleteSourceFiles = isFullFileMove && isDeleteEmptyFiles, deleteSourceFiles = isDeleteEmptyFiles,
moveCallback = moveCallback, moveCallback = moveCallback,
openInEditor = false, openInEditor = false,
allElementsToMove = null, allElementsToMove = null,
@@ -6,7 +6,10 @@
package org.jetbrains.kotlin.idea.refactoring.move package org.jetbrains.kotlin.idea.refactoring.move
import com.intellij.ide.util.DirectoryUtil import com.intellij.ide.util.DirectoryUtil
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Comparing import com.intellij.openapi.util.Comparing
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
@@ -58,7 +61,6 @@ import org.jetbrains.kotlin.utils.addIfNotNull
import java.io.File import java.io.File
import java.lang.System.currentTimeMillis import java.lang.System.currentTimeMillis
import java.util.* import java.util.*
import javax.swing.JCheckBox
sealed class ContainerInfo { sealed class ContainerInfo {
abstract val fqName: FqName? abstract val fqName: FqName?
@@ -708,4 +710,27 @@ internal fun logFusForMoveRefactoring(
isSucceeded = succeeded, isSucceeded = succeeded,
) )
} }
} }
internal fun <T> List<KtNamedDeclaration>.mapWithReadActionInProcess(
project: Project,
title: String,
body: (KtNamedDeclaration) -> T
): List<T> = let { declarations ->
val result = mutableListOf<T>()
val task: Task.Modal = object : Task.Modal(project, title, false) {
override fun run(indicator: ProgressIndicator) {
val count = 0
val fraction: Double = 1.0 / declarations.size
indicator.fraction = 0.0
ApplicationManager.getApplication().runReadAction(Runnable {
for (declaration in declarations) {
result.add(body(declaration))
indicator.fraction = fraction * count
}
})
}
}
ProgressManager.getInstance().run(task)
return result
}