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" +
"isSearchInComments = $isSearchInComments\n" +
"isSearchInNonJavaFiles = $isSearchInNonJavaFiles\n" +
"isDeleteEmptyFiles = $isDeleteEmptyFiles\n" +
"isUpdatePackageDirective = $isUpdatePackageDirective\n" +
"isFullFileMove = $isFullFileMove"
"isDeleteEmptyFiles = $isDeleteEmptyFiles\n"
}
private fun KotlinAwareMoveFilesOrDirectoriesModel.testDataString(): String {
@@ -153,10 +151,6 @@ internal class MoveKotlinDeclarationsHandlerTestActions(private val caseDataKeep
targetDirectory: PsiDirectory?,
targetFile: KtFile?,
moveToPackage: Boolean,
searchInComments: Boolean,
searchForTextOccurrences: Boolean,
deleteEmptySourceFiles: Boolean,
moveMppDeclarations: Boolean,
moveCallback: MoveCallback?
) {
val selectedElementsToMove = mutableSetOf<KtNamedDeclaration>()
@@ -180,8 +174,6 @@ internal class MoveKotlinDeclarationsHandlerTestActions(private val caseDataKeep
isSearchInComments = randomBoolean(),
isSearchInNonJavaFiles = randomBoolean(),
isDeleteEmptyFiles = randomBoolean(),
isUpdatePackageDirective = randomBoolean(),
isFullFileMove = randomBoolean(),
applyMPPDeclarations = true,
moveCallback = null
)
@@ -68,10 +68,6 @@ private val defaultHandlerActions = object : MoveKotlinDeclarationsHandlerAction
targetDirectory: PsiDirectory?,
targetFile: KtFile?,
moveToPackage: Boolean,
searchInComments: Boolean,
searchForTextOccurrences: Boolean,
deleteEmptySourceFiles: Boolean,
moveMppDeclarations: Boolean,
moveCallback: MoveCallback?
) = MoveKotlinTopLevelDeclarationsDialog(
project,
@@ -80,10 +76,6 @@ private val defaultHandlerActions = object : MoveKotlinDeclarationsHandlerAction
targetDirectory,
targetFile,
moveToPackage,
searchInComments,
searchForTextOccurrences,
deleteEmptySourceFiles,
moveMppDeclarations,
moveCallback
).show()
@@ -180,6 +172,15 @@ class MoveKotlinDeclarationsHandler internal constructor(private val handlerActi
}
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(
project, initialTargetDirectory, ktFileElements, callback
)
@@ -193,10 +194,6 @@ class MoveKotlinDeclarationsHandler internal constructor(private val handlerActi
val targetDirectory = if (targetContainer != null) {
MoveClassesOrPackagesImpl.getInitialTargetDirectory(targetContainer, elements)
} 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 moveToPackage = targetContainer !is KtFile
@@ -207,10 +204,6 @@ class MoveKotlinDeclarationsHandler internal constructor(private val handlerActi
targetDirectory,
targetFile,
moveToPackage,
searchInComments,
searchInText,
deleteEmptySourceFiles,
moveMppDeclarations,
callback
)
}
@@ -31,10 +31,6 @@ internal interface MoveKotlinDeclarationsHandlerActions {
targetDirectory: PsiDirectory?,
targetFile: KtFile?,
moveToPackage: Boolean,
searchInComments: Boolean,
searchForTextOccurrences: Boolean,
deleteEmptySourceFiles: Boolean,
moveMppDeclarations: Boolean,
moveCallback: MoveCallback?
)
@@ -363,7 +363,7 @@ class MoveKotlinDeclarationsProcessor(
}
}
if (descriptor.deleteSourceFiles) {
if (descriptor.deleteSourceFiles && sourceFile.declarations.isEmpty()) {
sourceFile.delete()
}
}
@@ -66,22 +66,19 @@ class MoveToKotlinFileProcessor @JvmOverloads constructor(
return super.showConflicts(conflicts, usages)
}
// Assign a temporary name to file-under-move to avoid naming conflict during the refactoring
private fun renameFileTemporarily() {
if (targetDirectory.findFile(targetFileName) == null) return
private fun renameFileTemporarilyIfNeeded() {
if (targetDirectory.findFile(sourceFile.name) == null) return
val containingDirectory = sourceFile.containingDirectory ?: return
val temporaryName = UniqueNameGenerator.generateUniqueName("temp", "", ".kt") {
sourceFile.containingDirectory!!.findFile(it) == null
containingDirectory.findFile(it) == null
}
sourceFile.name = temporaryName
}
override fun performRefactoring(usages: Array<UsageInfo>) {
val needTemporaryRename = targetDirectory.findFile(sourceFile.name) != null
if (needTemporaryRename) {
renameFileTemporarily()
}
renameFileTemporarilyIfNeeded()
super.performRefactoring(usages)
sourceFile.name = targetFileName
}
@@ -129,9 +129,11 @@ internal abstract class MoveKotlinNestedClassesToUpperLevelModel(
if (targetContainer is KtClassOrObject) {
val targetClass = targetContainer as KtClassOrObject?
for (member in targetClass!!.declarations) {
if (member is KtClassOrObject && className == member.getName()) {
throw ConfigurationException(RefactoringBundle.message("inner.class.exists", className, targetClass.name))
if (targetClass != null) {
for (member in targetClass.declarations) {
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"/>
</properties>
</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">
<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">
@@ -72,16 +81,6 @@
<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 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">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
@@ -107,15 +106,6 @@
</component>
</children>
</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">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
@@ -16,7 +16,6 @@ import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.classMembers.AbstractMemberInfoModel;
import com.intellij.refactoring.classMembers.MemberInfoBase;
import com.intellij.refactoring.classMembers.MemberInfoChange;
import com.intellij.refactoring.classMembers.MemberInfoChangeListener;
import com.intellij.refactoring.move.MoveCallback;
import com.intellij.refactoring.move.MoveHandler;
import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo;
@@ -31,7 +30,6 @@ import kotlin.collections.CollectionsKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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.refactoring.KotlinRefactoringSettings;
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo;
@@ -49,8 +47,10 @@ import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.util.*;
import java.util.BitSet;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static org.jetbrains.kotlin.idea.roots.ProjectRootUtilsKt.getSuitableDestinationSourceRoots;
@@ -69,44 +69,31 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
private TextFieldWithBrowseButton fileChooser;
private JPanel memberInfoPanel;
private JTextField tfFileNameInPackage;
private JCheckBox cbSpecifyFileNameInPackage;
private JCheckBox cbUpdatePackageDirective;
private JCheckBox cbDeleteEmptySourceFiles;
private JCheckBox cbSearchReferences;
private JCheckBox cbApplyMPPDeclarationsMove;
private KotlinMemberSelectionTable memberTable;
private class MemberSelectionerInfoChangeListener implements MemberInfoChangeListener<KtNamedDeclaration, KotlinMemberInfo> {
private final List<KotlinMemberInfo> memberInfos;
public MemberSelectionerInfoChangeListener(List<KotlinMemberInfo> memberInfos) {
this.memberInfos = memberInfos;
}
private boolean shouldUpdateFileNameField(Collection<KotlinMemberInfo> changedMembers) {
if (!tfFileNameInPackage.isEnabled()) return true;
Collection<KtNamedDeclaration> previousDeclarations = CollectionsKt.filterNotNull(
CollectionsKt.map(
memberInfos,
info -> changedMembers.contains(info) != info.isChecked() ? info.getMember() : null
)
);
String suggestedText = previousDeclarations.isEmpty() ? "" : MoveUtilsKt.guessNewFileName(previousDeclarations);
return tfFileNameInPackage.getText().equals(suggestedText);
}
@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(
@NotNull Project project,
@NotNull Set<KtNamedDeclaration> elementsToMove,
@Nullable String targetPackageName,
@Nullable PsiDirectory targetDirectory,
@Nullable KtFile targetFile,
boolean moveToPackage,
@Nullable MoveCallback moveCallback
) {
this(project,
elementsToMove,
targetPackageName,
targetDirectory,
targetFile,
moveToPackage,
KotlinRefactoringSettings.getInstance().MOVE_SEARCH_IN_COMMENTS,
KotlinRefactoringSettings.getInstance().MOVE_SEARCH_FOR_TEXT,
KotlinRefactoringSettings.getInstance().MOVE_DELETE_EMPTY_SOURCE_FILES,
KotlinRefactoringSettings.getInstance().MOVE_MPP_DECLARATIONS,
moveCallback);
}
public MoveKotlinTopLevelDeclarationsDialog(
@@ -124,16 +111,19 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
) {
super(project, true);
init();
List<KtFile> sourceFiles = getSourceFiles(elementsToMove);
this.moveCallback = moveCallback;
this.initialTargetDirectory = targetDirectory;
init();
setTitle(MoveHandler.REFACTORING_NAME);
initSearchOptions(searchInComments, searchForTextOccurrences, deleteEmptySourceFiles, moveMppDeclarations);
List<KtNamedDeclaration> allDeclarations = getAllDeclarations(sourceFiles);
initSearchOptions(searchInComments, searchForTextOccurrences, deleteEmptySourceFiles, moveMppDeclarations, allDeclarations);
initPackageChooser(targetPackageName, targetDirectory, sourceFiles);
@@ -141,25 +131,28 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
initMoveToButtons(moveToPackage);
initMemberInfo(elementsToMove, sourceFiles);
initMemberInfo(elementsToMove, allDeclarations);
updateControls();
initializedCheckBoxesState = getCheckboxesState(true);
}
@Override
protected void init() {
super.init();
}
private final BitSet initializedCheckBoxesState;
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(1, applyDefaults || cbSearchTextOccurrences.isSelected()); //cbSearchTextOccurrences 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(4, cbSpecifyFileNameInPackage.isSelected());
state.set(5, cbUpdatePackageDirective.isSelected());
state.set(6, cbSearchReferences.isSelected());
state.set(4, cbSearchReferences.isSelected());
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(
@NotNull Set<KtNamedDeclaration> elementsToMove,
@NotNull List<KtFile> sourceFiles
@NotNull List<KtNamedDeclaration> declarations
) {
List<KotlinMemberInfo> memberInfos = CollectionsKt.map(
getAllDeclarations(sourceFiles),
declaration -> {
KotlinMemberInfo memberInfo = new KotlinMemberInfo(declaration, false);
memberInfo.setChecked(elementsToMove.contains(declaration));
return memberInfo;
}
);
////KotlinMemberInfo run resolve on declaration so it is good to place it to the process
List<KotlinMemberInfo> memberInfos = MoveUtilsKt.mapWithReadActionInProcess(declarations, myProject, MoveHandler.REFACTORING_NAME, (declaration) -> {
KotlinMemberInfo memberInfo = new KotlinMemberInfo(declaration, false);
memberInfo.setChecked(elementsToMove.contains(declaration));
return memberInfo;
});
KotlinMemberSelectionPanel selectionPanel = new KotlinMemberSelectionPanel(getTitle(), memberInfos, null);
memberTable = selectionPanel.getTable();
MemberInfoModelImpl memberInfoModel = new MemberInfoModelImpl();
memberInfoModel.memberInfoChanged(new MemberInfoChange<>(memberInfos));
selectionPanel.getTable().setMemberInfoModel(memberInfoModel);
selectionPanel.getTable().addMemberInfoChangeListener(memberInfoModel);
selectionPanel.getTable().addMemberInfoChangeListener(new MemberSelectionerInfoChangeListener(memberInfos));
selectionPanel.getTable().addMemberInfoChangeListener(listener -> updateControls());
cbApplyMPPDeclarationsMove.addChangeListener(e -> updateControls());
memberInfoPanel.add(selectionPanel, BorderLayout.CENTER);
}
@@ -219,8 +204,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
private void updateFileNameInPackageField() {
boolean movingSingleFileToPackage = rbMoveToPackage.isSelected() && getSourceFiles(getSelectedElementsToMove()).size() == 1;
cbSpecifyFileNameInPackage.setEnabled(movingSingleFileToPackage);
tfFileNameInPackage.setEnabled(movingSingleFileToPackage && cbSpecifyFileNameInPackage.isSelected());
tfFileNameInPackage.setEnabled(movingSingleFileToPackage);
}
private void initPackageChooser(
@@ -243,17 +227,20 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
},
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);
cbSearchTextOccurrences.setSelected(searchForTextOccurences);
cbDeleteEmptySourceFiles.setSelected(deleteEmptySourceFiles);
cbApplyMPPDeclarationsMove.setSelected(moveMppDeclarations);
cbApplyMPPDeclarationsMove.setVisible(isMPPDeclarationInList(allDeclarations));
}
private void initMoveToButtons(boolean moveToPackage) {
@@ -348,8 +335,8 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
);
}
private boolean isMppDeclarationSelected() {
for(KtNamedDeclaration element : getSelectedElementsToMove()) {
private boolean isMPPDeclarationInList(List<KtNamedDeclaration> declarations) {
for(KtNamedDeclaration element : declarations) {
if (ExpectActualUtilKt.isEffectivelyActual(element, true) ||
ExpectActualUtilKt.isExpectDeclaration(element)) {
return true;
@@ -358,6 +345,10 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
return false;
}
private boolean isMppDeclarationSelected() {
return isMPPDeclarationInList(getSelectedElementsToMove());
}
private void updateControls() {
boolean mppDeclarationSelected = isMppDeclarationSelected();
@@ -376,27 +367,11 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
classPackageChooser.setEnabled(moveToPackage);
updateFileNameInPackageField();
fileChooser.setEnabled(!moveToPackage);
updatePackageDirectiveCheckBox();
UIUtil.setEnabled(targetPanel, moveToPackage && hasAnySourceRoots(), true);
UIUtil.setEnabled(targetPanel, moveToPackage && !needToMoveMPPDeclarations && hasAnySourceRoots(), true);
updateSuggestedFileName();
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() {
return !getSuitableDestinationSourceRoots(myProject).isEmpty();
}
@@ -443,11 +418,14 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
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();
PsiDirectory selectedPsiDirectory = null;
if (!mppDeclarationSelected) {
DirectoryChooser.ItemWrapper selectedItem = (DirectoryChooser.ItemWrapper) destinationFolderCB.getComboBox().getSelectedItem();
selectedPsiDirectory = selectedItem != null ? selectedItem.getDirectory() : initialTargetDirectory;
}
List<KtNamedDeclaration> selectedElements = getSelectedElementsToMoveChecked();
return new MoveKotlinTopLevelDeclarationsModel(
@@ -462,8 +440,6 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
cbSearchInComments.isSelected(),
cbSearchTextOccurrences.isSelected(),
cbDeleteEmptySourceFiles.isSelected(),
cbUpdatePackageDirective.isSelected(),
isFullFileMove(),
mppDeclarationSelected,
moveCallback
);
@@ -16,6 +16,7 @@ import com.intellij.refactoring.BaseRefactoringProcessor
import com.intellij.refactoring.MoveDestination
import com.intellij.refactoring.PackageWrapper
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.MultipleRootsMoveDestination
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.refactoring.getOrCreateKotlinFile
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.updatePackageDirective
import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector.MovedEntity
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.isEffectivelyActual
import org.jetbrains.kotlin.idea.util.isExpectDeclaration
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getFileOrScriptDeclarations
import java.io.File
import java.nio.file.InvalidPathException
import java.nio.file.Paths
@@ -53,8 +53,6 @@ internal class MoveKotlinTopLevelDeclarationsModel(
val isSearchInComments: Boolean,
val isSearchInNonJavaFiles: Boolean,
val isDeleteEmptyFiles: Boolean,
val isUpdatePackageDirective: Boolean,
val isFullFileMove: Boolean,
val applyMPPDeclarations: Boolean,
val moveCallback: MoveCallback?
) : Model {
@@ -65,9 +63,13 @@ internal class MoveKotlinTopLevelDeclarationsModel(
private fun checkedGetSourceDirectory() = sourceFiles.mapToSingleOrNull { it.parent }
?: 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
@@ -121,15 +123,17 @@ internal class MoveKotlinTopLevelDeclarationsModel(
}
private fun selectMoveTargetToPackage(): KotlinMoveTarget {
require(sourceFiles.isNotEmpty())
val moveDestination = selectPackageBasedDestination()
val targetDirectory: PsiDirectory? = moveDestination.getTargetIfExists(checkedGetSourceDirectory())
val targetFileName = if (singleSourceFileMode) fileNameInPackage.also(::checkTargetFileName) else null
if (targetDirectory != null) {
tryMoveToPackageForExistingDirectory(targetFileName, targetDirectory)?.let { return it }
val targetDirectory: PsiDirectory?
if (!elementsToMoveHasMPP) {
targetDirectory = moveDestination.getTargetIfExists(checkedGetSourceDirectory())
val targetFileName = if (singleSourceFileMode) fileNameInPackage.also(::checkTargetFileName) else null
if (targetDirectory != null) {
tryMoveToPackageForExistingDirectory(targetFileName, targetDirectory)?.let { return it }
}
} else {
targetDirectory = null
}
return KotlinMoveTargetForDeferredFile(
@@ -193,9 +197,6 @@ internal class MoveKotlinTopLevelDeclarationsModel(
}
}
private fun selectMoveTarget() =
if (isMoveToPackage) selectMoveTargetToPackage() else selectMoveTargetToFile()
private fun verifyBeforeRun() {
if (!isMoveToPackage && elementsToMoveHasMPP)
throw ConfigurationException(KotlinBundle.message("text.cannot.move.expect.actual.declaration.to.file"))
@@ -217,14 +218,7 @@ internal class MoveKotlinTopLevelDeclarationsModel(
}
}
@Throws(ConfigurationException::class)
override fun computeModelResult() = computeModelResult(throwOnConflicts = false)
@Throws(ConfigurationException::class)
override fun computeModelResult(throwOnConflicts: Boolean): ModelResultWithFUSData {
verifyBeforeRun()
private fun getFUSParameters(): Pair<MovedEntity, MoveRefactoringDestination> {
val classType = if (elementsToMoveHasMPP) MovedEntity.MPPCLASSES else MovedEntity.CLASSES
val functionType = if (elementsToMoveHasMPP) MovedEntity.MPPFUNCTIONS else MovedEntity.FUNCTIONS
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
if (isFullFileMove && isMoveToPackage) {
tryMoveFile(throwOnConflicts)?.let {
return ModelResultWithFUSData(it, elementsToMove.size, entity, destination)
}
}
return entity to 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)
}
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
if (targetFileName != null) checkTargetFileName(targetFileName)
val allDeclarationsMovingOut = elementsToMove
.groupBy { obj: KtPureElement -> obj.containingKtFile }
.all { it.key.getFileOrScriptDeclarations().size == it.value.size }
if (!allDeclarationsMovingOut) return null
val moveDestination = selectPackageBasedDestination()
val targetDirectory = moveDestination.getTargetIfExists(checkedGetSourceDirectory()) ?: return null
val targetDirectory = selectPackageBasedDestination()
.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() ||
filesExistingInTargetDir.singleOrNull()?.let { sourceFiles.contains(it) } == true
if (!moveAsFile) return null
sourceFiles.forEach { it.updatePackageDirective = isUpdatePackageDirective }
sourceFiles.forEach { it.updatePackageDirective = true }
return if (targetFileName != null)
return if (targetFileNameAndFile != null)
MoveToKotlinFileProcessor(
project,
sourceFiles.first(),
targetFileNameAndFile.second,
targetDirectory,
targetFileName,
targetFileNameAndFile.first,
searchInComments = isSearchInComments,
searchInNonJavaFiles = isSearchInNonJavaFiles,
moveCallback = moveCallback,
@@ -291,13 +302,16 @@ internal class MoveKotlinTopLevelDeclarationsModel(
private fun moveDeclaration(throwOnConflicts: Boolean): BaseRefactoringProcessor {
val elementsWithMPPIfNeeded =
if (elementsToMoveHasMPP) elementsToMove
.flatMap { it.collectAllExpectAndActualDeclaration() }
.filterIsInstance<KtNamedDeclaration>()
else elementsToMove
if (elementsToMoveHasMPP) require(isMoveToPackage && selectedPsiDirectory == null)
val target = if (isMoveToPackage) selectMoveTargetToPackage() else selectMoveTargetToFile()
val elementsWithMPPIfNeeded = if (elementsToMoveHasMPP)
elementsToMove.mapWithReadActionInProcess(project, MoveHandler.REFACTORING_NAME) {
it.collectAllExpectAndActualDeclaration()
}.flatten().filterIsInstance<KtNamedDeclaration>()
else elementsToMove
val target = selectMoveTarget()
for (element in elementsWithMPPIfNeeded) {
target.verify(element.containingFile)?.let { throw ConfigurationException(it) }
}
@@ -309,7 +323,7 @@ internal class MoveKotlinTopLevelDeclarationsModel(
MoveDeclarationsDelegate.TopLevel,
isSearchInComments,
isSearchInNonJavaFiles,
deleteSourceFiles = isFullFileMove && isDeleteEmptyFiles,
deleteSourceFiles = isDeleteEmptyFiles,
moveCallback = moveCallback,
openInEditor = false,
allElementsToMove = null,
@@ -6,7 +6,10 @@
package org.jetbrains.kotlin.idea.refactoring.move
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.Task
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Comparing
import com.intellij.openapi.util.Key
@@ -58,7 +61,6 @@ import org.jetbrains.kotlin.utils.addIfNotNull
import java.io.File
import java.lang.System.currentTimeMillis
import java.util.*
import javax.swing.JCheckBox
sealed class ContainerInfo {
abstract val fqName: FqName?
@@ -708,4 +710,27 @@ internal fun logFusForMoveRefactoring(
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
}