Move Declarations: Support multiple files
#KT-8431 Fixed
This commit is contained in:
+8
-4
@@ -23,6 +23,7 @@ import com.intellij.psi.PsiManager
|
||||
import com.intellij.refactoring.PackageWrapper
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.JetFile
|
||||
import java.util.*
|
||||
|
||||
public interface KotlinMoveTarget {
|
||||
val packageWrapper: PackageWrapper?
|
||||
@@ -48,13 +49,16 @@ public class JetFileKotlinMoveTarget(val targetFile: JetFile): KotlinMoveTarget
|
||||
|
||||
public class DeferredJetFileKotlinMoveTarget(
|
||||
project: Project,
|
||||
val packageFqName: FqName,
|
||||
createFile: () -> JetFile?): KotlinMoveTarget {
|
||||
val createdFile: JetFile? by lazy(createFile)
|
||||
private val packageFqName: FqName,
|
||||
private val createFile: (JetFile) -> JetFile?): KotlinMoveTarget {
|
||||
private val createdFiles = HashMap<JetFile, JetFile?>()
|
||||
|
||||
override val packageWrapper: PackageWrapper = PackageWrapper(PsiManager.getInstance(project), packageFqName.asString())
|
||||
|
||||
override fun getOrCreateTargetPsi(originalPsi: PsiElement) = createdFile
|
||||
override fun getOrCreateTargetPsi(originalPsi: PsiElement): PsiFile? {
|
||||
val originalFile = originalPsi.containingFile as? JetFile ?: return null
|
||||
return createdFiles.getOrPut(originalFile) { createFile(originalFile) }
|
||||
}
|
||||
|
||||
override fun getTargetPsiIfExists(originalPsi: PsiElement) = null
|
||||
|
||||
|
||||
+14
-17
@@ -20,42 +20,39 @@ import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.actionSystem.LangDataKeys
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.*
|
||||
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.getPackage
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.isInJavaSourceRoot
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.ui.MoveKotlinTopLevelDeclarationsDialog
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.idea.core.getPackage
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.guessNewFileName
|
||||
import java.util.HashSet
|
||||
import java.util.LinkedHashSet
|
||||
|
||||
public class MoveKotlinTopLevelDeclarationsHandler : MoveHandlerDelegate() {
|
||||
private fun getSourceDirectories(elements: Array<out PsiElement>) = elements.mapTo(LinkedHashSet()) { it.containingFile?.parent }
|
||||
|
||||
private fun doMoveWithCheck(
|
||||
project: Project, elements: Array<out PsiElement>, targetContainer: PsiElement?, callback: MoveCallback?
|
||||
): Boolean {
|
||||
if (!CommonRefactoringUtil.checkReadOnlyStatusRecursively(project, elements.toList(), true)) return false
|
||||
|
||||
@suppress("UNCHECKED_CAST")
|
||||
val elementsToSearch = elements.toSet() as Set<JetNamedDeclaration>
|
||||
|
||||
val sourceFiles = elementsToSearch.mapTo(LinkedHashSet<JetFile>()) { it.getContainingJetFile() }
|
||||
val sourceFile = sourceFiles.singleOrNull()
|
||||
if (sourceFile == null) {
|
||||
if (getSourceDirectories(elements).singleOrNull() == null) {
|
||||
CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("move.title"),
|
||||
"All declarations must belong to the same file",
|
||||
"All declarations must belong to the same directory",
|
||||
null,
|
||||
project)
|
||||
return false
|
||||
}
|
||||
|
||||
val elementsToSearch = elements.mapTo(LinkedHashSet()) { it as JetNamedDeclaration }
|
||||
|
||||
val targetPackageName = MoveClassesOrPackagesImpl.getInitialTargetPackageName(targetContainer, elements)
|
||||
val targetDirectory = MoveClassesOrPackagesImpl.getInitialTargetDirectory(targetContainer, elements)
|
||||
val searchInComments = JavaRefactoringSettings.getInstance()!!.MOVE_SEARCH_IN_COMMENTS
|
||||
@@ -64,7 +61,7 @@ public class MoveKotlinTopLevelDeclarationsHandler : MoveHandlerDelegate() {
|
||||
val moveToPackage = targetContainer !is JetFile
|
||||
|
||||
MoveKotlinTopLevelDeclarationsDialog(
|
||||
project, sourceFile, elementsToSearch, targetPackageName, targetDirectory, targetFile, moveToPackage, searchInComments, searchInText, callback
|
||||
project, elementsToSearch, targetPackageName, targetDirectory, targetFile, moveToPackage, searchInComments, searchInText, callback
|
||||
).show()
|
||||
|
||||
return true
|
||||
@@ -72,11 +69,11 @@ public class MoveKotlinTopLevelDeclarationsHandler : MoveHandlerDelegate() {
|
||||
|
||||
override fun canMove(elements: Array<out PsiElement>, targetContainer: PsiElement?): Boolean {
|
||||
if (!super.canMove(elements, targetContainer)) return false
|
||||
if (elements.map { it.getContainingFile() }.distinct().size() != 1) return false
|
||||
if (getSourceDirectories(elements).singleOrNull() == null) return false
|
||||
|
||||
return elements.all { e ->
|
||||
if (e is JetClass || (e is JetObjectDeclaration && !e.isObjectLiteral()) || e is JetNamedFunction || e is JetProperty) {
|
||||
val parent = e.getParent()
|
||||
val parent = e.parent
|
||||
parent is JetFile && parent.isInJavaSourceRoot()
|
||||
}
|
||||
else false
|
||||
|
||||
+241
-108
@@ -31,13 +31,15 @@ import com.intellij.openapi.ui.TextComponentAccessor;
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
|
||||
import com.intellij.openapi.util.EmptyRunnable;
|
||||
import com.intellij.openapi.util.Pass;
|
||||
import com.intellij.openapi.util.io.FileUtilRt;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.refactoring.*;
|
||||
import com.intellij.refactoring.classMembers.DependencyMemberInfoModel;
|
||||
import com.intellij.refactoring.JavaRefactoringSettings;
|
||||
import com.intellij.refactoring.MoveDestination;
|
||||
import com.intellij.refactoring.PackageWrapper;
|
||||
import com.intellij.refactoring.RefactoringBundle;
|
||||
import com.intellij.refactoring.classMembers.AbstractMemberInfoModel;
|
||||
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;
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.DestinationFolderComboBox;
|
||||
@@ -49,6 +51,7 @@ import com.intellij.ui.ComboboxWithBrowseButton;
|
||||
import com.intellij.ui.RecentsManager;
|
||||
import com.intellij.ui.ReferenceEditorComboWithBrowseButton;
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.text.UniqueNameGenerator;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
@@ -62,10 +65,10 @@ import org.jetbrains.kotlin.idea.core.CorePackage;
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.RefactoringPackage;
|
||||
import org.jetbrains.kotlin.idea.refactoring.JetRefactoringBundle;
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo;
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.MovePackage;
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.*;
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionPanel;
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionTable;
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.MovePackage;
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.*;
|
||||
import org.jetbrains.kotlin.idea.util.application.ApplicationPackage;
|
||||
import org.jetbrains.kotlin.name.FqName;
|
||||
import org.jetbrains.kotlin.psi.JetFile;
|
||||
@@ -76,28 +79,14 @@ import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
private static final String RECENTS_KEY = "MoveKotlinTopLevelDeclarationsDialog.RECENTS_KEY";
|
||||
|
||||
private class MemberInfoModelImpl extends DependencyMemberInfoModel<JetNamedDeclaration, KotlinMemberInfo> {
|
||||
public MemberInfoModelImpl() {
|
||||
super(new UsesMemberDependencyGraph<JetNamedDeclaration, JetFile, KotlinMemberInfo>(sourceFile, null, false), WARNING);
|
||||
}
|
||||
private static class MemberInfoModelImpl extends AbstractMemberInfoModel<JetNamedDeclaration, KotlinMemberInfo> {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Boolean isFixedAbstract(KotlinMemberInfo member) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCheckedWhenDisabled(KotlinMemberInfo member) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private JCheckBox cbSearchInComments;
|
||||
@@ -115,12 +104,10 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
private JCheckBox cbUpdatePackageDirective;
|
||||
private KotlinMemberSelectionTable memberTable;
|
||||
|
||||
private final JetFile sourceFile;
|
||||
private final MoveCallback moveCallback;
|
||||
|
||||
public MoveKotlinTopLevelDeclarationsDialog(
|
||||
@NotNull Project project,
|
||||
@NotNull JetFile sourceFile,
|
||||
@NotNull Set<JetNamedDeclaration> elementsToMove,
|
||||
@Nullable String targetPackageName,
|
||||
@Nullable PsiDirectory targetDirectory,
|
||||
@@ -132,7 +119,8 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
) {
|
||||
super(project, true);
|
||||
|
||||
this.sourceFile = sourceFile;
|
||||
List<JetFile> sourceFiles = getSourceFiles(elementsToMove);
|
||||
|
||||
this.moveCallback = moveCallback;
|
||||
|
||||
init();
|
||||
@@ -141,22 +129,78 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
|
||||
initSearchOptions(searchInComments, searchForTextOccurences);
|
||||
|
||||
initPackageChooser(targetPackageName, targetDirectory);
|
||||
initPackageChooser(targetPackageName, targetDirectory, sourceFiles);
|
||||
|
||||
initFileChooser(targetFile, elementsToMove);
|
||||
initFileChooser(targetFile, elementsToMove, sourceFiles);
|
||||
|
||||
initMoveToButtons(moveToPackage);
|
||||
|
||||
initMemberInfo(elementsToMove);
|
||||
initMemberInfo(elementsToMove, sourceFiles);
|
||||
|
||||
updateControls();
|
||||
|
||||
pack();
|
||||
}
|
||||
|
||||
private void initMemberInfo(@NotNull final Set<JetNamedDeclaration> elementsToMove) {
|
||||
private static List<JetFile> getSourceFiles(@NotNull Collection<JetNamedDeclaration> elementsToMove) {
|
||||
return KotlinPackage.distinct(
|
||||
KotlinPackage.map(
|
||||
elementsToMove,
|
||||
new Function1<JetNamedDeclaration, JetFile>() {
|
||||
@Override
|
||||
public JetFile invoke(JetNamedDeclaration declaration) {
|
||||
return declaration.getContainingJetFile();
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiDirectory getSourceDirectory(@NotNull Collection<JetFile> sourceFiles) {
|
||||
return KotlinPackage.single(
|
||||
KotlinPackage.distinct(
|
||||
KotlinPackage.map(
|
||||
sourceFiles,
|
||||
new Function1<JetFile, PsiDirectory>() {
|
||||
@Override
|
||||
public PsiDirectory invoke(JetFile jetFile) {
|
||||
return jetFile.getParent();
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private static List<JetNamedDeclaration> getAllDeclarations(Collection<JetFile> sourceFiles) {
|
||||
return KotlinPackage.filterIsInstance(
|
||||
KotlinPackage.flatMap(
|
||||
sourceFiles,
|
||||
new Function1<JetFile, Iterable<?>>() {
|
||||
@Override
|
||||
public Iterable<?> invoke(JetFile jetFile) {
|
||||
return jetFile.getDeclarations();
|
||||
}
|
||||
}
|
||||
),
|
||||
JetNamedDeclaration.class
|
||||
);
|
||||
}
|
||||
|
||||
private static boolean arePackagesAndDirectoryMatched(List<JetFile> sourceFiles) {
|
||||
for (JetFile sourceFile : sourceFiles) {
|
||||
if (!CorePackage.packageMatchesDirectory(sourceFile)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void initMemberInfo(
|
||||
@NotNull final Set<JetNamedDeclaration> elementsToMove,
|
||||
@NotNull List<JetFile> sourceFiles
|
||||
) {
|
||||
final List<KotlinMemberInfo> memberInfos = KotlinPackage.map(
|
||||
KotlinPackage.filterIsInstance(sourceFile.getDeclarations(), JetNamedDeclaration.class),
|
||||
getAllDeclarations(sourceFiles),
|
||||
new Function1<JetNamedDeclaration, KotlinMemberInfo>() {
|
||||
@Override
|
||||
public KotlinMemberInfo invoke(JetNamedDeclaration declaration) {
|
||||
@@ -190,13 +234,14 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
);
|
||||
String suggestedText = previousDeclarations.isEmpty()
|
||||
? ""
|
||||
: MovePackage.guessNewFileName(sourceFile, previousDeclarations);
|
||||
: MovePackage.guessNewFileName(previousDeclarations);
|
||||
return tfFileNameInPackage.getText().equals(suggestedText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void memberInfoChanged(MemberInfoChange<JetNamedDeclaration, KotlinMemberInfo> event) {
|
||||
updatePackageDirectiveCheckBox();
|
||||
updateFileNameInPackageField();
|
||||
// Update file name field only if it user hasn't changed it to some non-default value
|
||||
if (shouldUpdateFileNameField(event.getChangedMembers())) {
|
||||
updateSuggestedFileName();
|
||||
@@ -205,19 +250,25 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
}
|
||||
);
|
||||
memberInfoPanel.add(selectionPanel, BorderLayout.CENTER);
|
||||
|
||||
}
|
||||
|
||||
private void updateSuggestedFileName() {
|
||||
tfFileNameInPackage.setText(MovePackage.guessNewFileName(sourceFile, getSelectedElementsToMove()));
|
||||
tfFileNameInPackage.setText(MovePackage.guessNewFileName(getSelectedElementsToMove()));
|
||||
|
||||
}
|
||||
|
||||
private void updateFileNameInPackageField() {
|
||||
cbSpecifyFileNameInPackage.setEnabled(isMoveToPackage());
|
||||
tfFileNameInPackage.setEnabled(isMoveToPackage() && cbSpecifyFileNameInPackage.isSelected());
|
||||
boolean movingSingleFileToPackage = isMoveToPackage()
|
||||
&& getSourceFiles(getSelectedElementsToMove()).size() == 1;
|
||||
cbSpecifyFileNameInPackage.setEnabled(movingSingleFileToPackage);
|
||||
tfFileNameInPackage.setEnabled(movingSingleFileToPackage && cbSpecifyFileNameInPackage.isSelected());
|
||||
}
|
||||
|
||||
private void initPackageChooser(String targetPackageName, PsiDirectory targetDirectory) {
|
||||
private void initPackageChooser(
|
||||
String targetPackageName,
|
||||
PsiDirectory targetDirectory,
|
||||
List<JetFile> sourceFiles
|
||||
) {
|
||||
if (targetPackageName != null) {
|
||||
classPackageChooser.prependItem(targetPackageName);
|
||||
}
|
||||
@@ -243,7 +294,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
}
|
||||
);
|
||||
|
||||
cbUpdatePackageDirective.setSelected(CorePackage.packageMatchesDirectory(sourceFile));
|
||||
cbUpdatePackageDirective.setSelected(arePackagesAndDirectoryMatched(sourceFiles));
|
||||
}
|
||||
|
||||
private void initSearchOptions(boolean searchInComments, boolean searchForTextOccurences) {
|
||||
@@ -280,7 +331,11 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
);
|
||||
}
|
||||
|
||||
private void initFileChooser(@Nullable JetFile targetFile, @NotNull Set<JetNamedDeclaration> elementsToMove) {
|
||||
private void initFileChooser(
|
||||
@Nullable JetFile targetFile,
|
||||
@NotNull Set<JetNamedDeclaration> elementsToMove,
|
||||
@NotNull List<JetFile> sourceFiles
|
||||
) {
|
||||
FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()
|
||||
.withRoots(ProjectRootManager.getInstance(myProject).getContentRoots())
|
||||
.withTreeRootVisible(true);
|
||||
@@ -291,7 +346,9 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
String initialTargetPath =
|
||||
targetFile != null
|
||||
? targetFile.getVirtualFile().getPath()
|
||||
: sourceFile.getVirtualFile().getParent().getPath() + "/" + MovePackage.guessNewFileName(sourceFile, elementsToMove);
|
||||
: sourceFiles.get(0).getVirtualFile().getParent().getPath() +
|
||||
"/" +
|
||||
MovePackage.guessNewFileName(elementsToMove);
|
||||
fileChooser.setText(initialTargetPath);
|
||||
}
|
||||
|
||||
@@ -331,8 +388,24 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
validateButtons();
|
||||
}
|
||||
|
||||
private boolean isFullFileMove() {
|
||||
Map<JetFile, List<? extends JetNamedDeclaration>> fileToElements = KotlinPackage.groupBy(
|
||||
getSelectedElementsToMove(),
|
||||
new Function1<JetNamedDeclaration, JetFile>() {
|
||||
@Override
|
||||
public JetFile invoke(JetNamedDeclaration declaration) {
|
||||
return declaration.getContainingJetFile();
|
||||
}
|
||||
}
|
||||
);
|
||||
for (Map.Entry<JetFile, List<? extends JetNamedDeclaration>> entry : fileToElements.entrySet()) {
|
||||
if (entry.getKey().getDeclarations().size() != entry.getValue().size()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updatePackageDirectiveCheckBox() {
|
||||
cbUpdatePackageDirective.setEnabled(isMoveToPackage() && getSelectedElementsToMove().size() == sourceFile.getDeclarations().size());
|
||||
cbUpdatePackageDirective.setEnabled(isMoveToPackage() && isFullFileMove());
|
||||
}
|
||||
|
||||
private boolean hasAnySourceRoots() {
|
||||
@@ -367,6 +440,40 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<PsiFile> getFilesExistingInTargetDir(
|
||||
@NotNull List<JetFile> sourceFiles,
|
||||
@Nullable String targetFileName,
|
||||
@Nullable final PsiDirectory targetDirectory
|
||||
) {
|
||||
if (targetDirectory == null) return Collections.emptyList();
|
||||
|
||||
List<String> fileNames =
|
||||
targetFileName != null
|
||||
? Collections.singletonList(targetFileName)
|
||||
: KotlinPackage.map(
|
||||
sourceFiles,
|
||||
new Function1<JetFile, String>() {
|
||||
@Override
|
||||
public String invoke(JetFile jetFile) {
|
||||
return jetFile.getName();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return KotlinPackage.filterNotNull(
|
||||
KotlinPackage.map(
|
||||
fileNames,
|
||||
new Function1<String, PsiFile>() {
|
||||
@Override
|
||||
public PsiFile invoke(String s) {
|
||||
return targetDirectory.findFile(s);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private KotlinMoveTarget selectMoveTarget() {
|
||||
String message = verifyBeforeRun();
|
||||
@@ -377,39 +484,63 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
|
||||
setErrorText(null);
|
||||
|
||||
List<JetFile> sourceFiles = getSourceFiles(getSelectedElementsToMove());
|
||||
PsiDirectory sourceDirectory = getSourceDirectory(sourceFiles);
|
||||
|
||||
if (isMoveToPackage()) {
|
||||
final MoveDestination moveDestination = selectPackageBasedMoveDestination(true);
|
||||
if (moveDestination == null) return null;
|
||||
|
||||
final String targetFileName = tfFileNameInPackage.getText();
|
||||
if (!checkTargetFileName(targetFileName)) return null;
|
||||
final String targetFileName = sourceFiles.size() > 1 ? null : tfFileNameInPackage.getText();
|
||||
if (targetFileName != null && !checkTargetFileName(targetFileName)) return null;
|
||||
|
||||
PsiDirectory directory = moveDestination.getTargetIfExists(sourceFile);
|
||||
PsiFile targetFile = directory != null ? directory.findFile(targetFileName) : null;
|
||||
if (targetFile != null) {
|
||||
if (targetFile == sourceFile) {
|
||||
setErrorText("Can't move to the original file");
|
||||
PsiDirectory targetDirectory = moveDestination.getTargetIfExists(sourceDirectory);
|
||||
|
||||
List<PsiFile> filesExistingInTargetDir = getFilesExistingInTargetDir(sourceFiles, targetFileName, targetDirectory);
|
||||
if (!filesExistingInTargetDir.isEmpty()) {
|
||||
if (!KotlinPackage.intersect(sourceFiles, filesExistingInTargetDir).isEmpty()) {
|
||||
setErrorText("Can't move to the original file(s)");
|
||||
return null;
|
||||
}
|
||||
|
||||
String question = "File '" +
|
||||
directory.getVirtualFile().getPath() +
|
||||
"/" +
|
||||
targetFileName +
|
||||
"' already exists." +
|
||||
"Do you want to move selected declarations to this file?";
|
||||
int ret = Messages.showYesNoDialog(myProject, question, RefactoringBundle.message("move.title"), Messages.getQuestionIcon());
|
||||
if (filesExistingInTargetDir.size() > 1) {
|
||||
String filePathsToReport = StringUtil.join(
|
||||
filesExistingInTargetDir,
|
||||
new Function<PsiFile, String>() {
|
||||
@Override
|
||||
public String fun(PsiFile file) {
|
||||
return file.getVirtualFile().getPath();
|
||||
}
|
||||
},
|
||||
"\n"
|
||||
);
|
||||
Messages.showErrorDialog(
|
||||
myProject,
|
||||
"Cannot perform refactoring since the following files already exist:\n\n" + filePathsToReport,
|
||||
RefactoringBundle.message("move.title")
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
String question = String.format(
|
||||
"File '%s' already exists. Do you want to move selected declarations to this file?",
|
||||
filesExistingInTargetDir.get(0).getVirtualFile().getPath()
|
||||
);
|
||||
int ret =
|
||||
Messages.showYesNoDialog(myProject, question, RefactoringBundle.message("move.title"), Messages.getQuestionIcon());
|
||||
if (ret != Messages.YES) return null;
|
||||
}
|
||||
|
||||
return new DeferredJetFileKotlinMoveTarget(
|
||||
myProject,
|
||||
new FqName(getTargetPackage()),
|
||||
new Function0<JetFile>() {
|
||||
new Function1<JetFile, JetFile>() {
|
||||
@Override
|
||||
public JetFile invoke() {
|
||||
PsiDirectory directory = moveDestination.getTargetDirectory(sourceFile);
|
||||
return RefactoringPackage.getOrCreateKotlinFile(targetFileName, directory);
|
||||
public JetFile invoke(@NotNull JetFile originalFile) {
|
||||
return RefactoringPackage.getOrCreateKotlinFile(
|
||||
targetFileName != null ? targetFileName : originalFile.getName(),
|
||||
moveDestination.getTargetDirectory(originalFile)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -419,7 +550,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
if (!checkTargetFileName(targetFile.getName())) return null;
|
||||
JetFile jetFile = (JetFile) RefactoringPackage.toPsiFile(targetFile, myProject);
|
||||
if (jetFile != null) {
|
||||
if (jetFile == sourceFile) {
|
||||
if (sourceFiles.size() == 1 && sourceFiles.contains(jetFile)) {
|
||||
setErrorText("Can't move to the original file");
|
||||
return null;
|
||||
}
|
||||
@@ -427,14 +558,6 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
return new JetFileKotlinMoveTarget(jetFile);
|
||||
}
|
||||
|
||||
int ret = Messages.showYesNoDialog(
|
||||
myProject,
|
||||
JetRefactoringBundle.message("file.does.not.exist", targetFile.getName()),
|
||||
RefactoringBundle.message("move.title"),
|
||||
Messages.getQuestionIcon()
|
||||
);
|
||||
if (ret != Messages.YES) return null;
|
||||
|
||||
File targetDir = targetFile.getParentFile();
|
||||
final PsiDirectory psiDirectory = RefactoringPackage.toPsiDirectory(targetDir, myProject);
|
||||
assert psiDirectory != null : "No directory found: " + targetDir.getPath();
|
||||
@@ -448,9 +571,9 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
return new DeferredJetFileKotlinMoveTarget(
|
||||
myProject,
|
||||
new FqName(psiPackage.getQualifiedName()),
|
||||
new Function0<JetFile>() {
|
||||
new Function1<JetFile, JetFile>() {
|
||||
@Override
|
||||
public JetFile invoke() {
|
||||
public JetFile invoke(@NotNull JetFile originalFile) {
|
||||
return RefactoringPackage.getOrCreateKotlinFile(targetFile.getName(), psiDirectory);
|
||||
}
|
||||
}
|
||||
@@ -474,7 +597,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
}
|
||||
}
|
||||
|
||||
if (tfFileNameInPackage.getText().isEmpty()) {
|
||||
if (getSourceFiles(getSelectedElementsToMove()).size() == 1 && tfFileNameInPackage.getText().isEmpty()) {
|
||||
return "File name may not be empty";
|
||||
}
|
||||
|
||||
@@ -527,6 +650,8 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
saveRefactoringSettings();
|
||||
|
||||
List<JetNamedDeclaration> elementsToMove = getSelectedElementsToMove();
|
||||
final List<JetFile> sourceFiles = getSourceFiles(elementsToMove);
|
||||
final PsiDirectory sourceDirectory = getSourceDirectory(sourceFiles);
|
||||
|
||||
for (PsiElement element : elementsToMove) {
|
||||
String message = target.verify(element.getContainingFile());
|
||||
@@ -538,27 +663,32 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
|
||||
try {
|
||||
boolean deleteSourceFile = false;
|
||||
|
||||
if (elementsToMove.size() == sourceFile.getDeclarations().size()) {
|
||||
|
||||
if (isFullFileMove()) {
|
||||
if (isMoveToPackage()) {
|
||||
final MoveDestination moveDestination = selectPackageBasedMoveDestination(false);
|
||||
//noinspection ConstantConditions
|
||||
PsiDirectory targetDir = moveDestination.getTargetIfExists(sourceFile);
|
||||
final String targetFileName = tfFileNameInPackage.getText();
|
||||
if (targetDir == null || targetDir.findFile(targetFileName) == null) {
|
||||
PsiDirectory targetDir = moveDestination.getTargetIfExists(sourceDirectory);
|
||||
final String targetFileName = sourceFiles.size() > 1 ? null : tfFileNameInPackage.getText();
|
||||
List<PsiFile> filesExistingInTargetDir = getFilesExistingInTargetDir(sourceFiles, targetFileName, targetDir);
|
||||
if (filesExistingInTargetDir.isEmpty()) {
|
||||
PsiDirectory targetDirectory = ApplicationPackage.runWriteAction(
|
||||
new Function0<PsiDirectory>() {
|
||||
@Override
|
||||
public PsiDirectory invoke() {
|
||||
return moveDestination.getTargetDirectory(sourceFile);
|
||||
return moveDestination.getTargetDirectory(sourceDirectory);
|
||||
}
|
||||
}
|
||||
);
|
||||
MovePackage.setUpdatePackageDirective(sourceFile, cbUpdatePackageDirective.isSelected());
|
||||
|
||||
for (JetFile sourceFile : sourceFiles) {
|
||||
MovePackage.setUpdatePackageDirective(sourceFile, cbUpdatePackageDirective.isSelected());
|
||||
}
|
||||
|
||||
invokeRefactoring(
|
||||
new MoveFilesOrDirectoriesProcessor(
|
||||
myProject,
|
||||
new PsiElement[] {sourceFile},
|
||||
sourceFiles.toArray(new PsiElement[sourceFiles.size()]),
|
||||
targetDirectory,
|
||||
true,
|
||||
isSearchInComments(),
|
||||
@@ -567,7 +697,9 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
@Override
|
||||
public void refactoringCompleted() {
|
||||
try {
|
||||
sourceFile.setName(targetFileName);
|
||||
if (targetFileName != null) {
|
||||
KotlinPackage.single(sourceFiles).setName(targetFileName);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (moveCallback != null) {
|
||||
@@ -580,47 +712,48 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
) {
|
||||
@Override
|
||||
protected String getCommandName() {
|
||||
return "Move " + sourceFile.getName();
|
||||
return targetFileName != null ? "Move " + KotlinPackage.single(sourceFiles).getName() : "Move";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performRefactoring(UsageInfo[] usages) {
|
||||
//noinspection ConstantConditions
|
||||
String temporaryName = UniqueNameGenerator.generateUniqueName(
|
||||
"temp",
|
||||
"",
|
||||
".kt",
|
||||
KotlinPackage.map(
|
||||
sourceFile.getContainingDirectory().getFiles(),
|
||||
new Function1<PsiFile, String>() {
|
||||
@Override
|
||||
public String invoke(PsiFile file) {
|
||||
return file.getName();
|
||||
protected void performRefactoring(@NotNull UsageInfo[] usages) {
|
||||
if (targetFileName != null) {
|
||||
JetFile sourceFile = KotlinPackage.single(sourceFiles);
|
||||
//noinspection ConstantConditions
|
||||
String temporaryName = UniqueNameGenerator.generateUniqueName(
|
||||
"temp",
|
||||
"",
|
||||
".kt",
|
||||
KotlinPackage.map(
|
||||
sourceFile.getContainingDirectory().getFiles(),
|
||||
new Function1<PsiFile, String>() {
|
||||
@Override
|
||||
public String invoke(PsiFile file) {
|
||||
return file.getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
sourceFile.setName(temporaryName);
|
||||
)
|
||||
);
|
||||
sourceFile.setName(temporaryName);
|
||||
}
|
||||
|
||||
super.performRefactoring(usages);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int ret = Messages.showYesNoCancelDialog(
|
||||
myProject,
|
||||
"You are going to move all declarations out of '" +
|
||||
sourceFile.getVirtualFile().getPath() +
|
||||
"'. Do you want to delete this file?",
|
||||
RefactoringBundle.message("move.title"),
|
||||
Messages.getQuestionIcon()
|
||||
);
|
||||
if (ret == Messages.CANCEL) return;
|
||||
deleteSourceFile = ret == Messages.YES;
|
||||
}
|
||||
|
||||
int ret = Messages.showYesNoCancelDialog(
|
||||
myProject,
|
||||
"You are about to move all declarations out of the source file(s). Do you want to delete empty files?",
|
||||
RefactoringBundle.message("move.title"),
|
||||
Messages.getQuestionIcon()
|
||||
);
|
||||
if (ret == Messages.CANCEL) return;
|
||||
deleteSourceFile = ret == Messages.YES;
|
||||
}
|
||||
|
||||
MoveKotlinTopLevelDeclarationsOptions options = new MoveKotlinTopLevelDeclarationsOptions(
|
||||
|
||||
@@ -17,18 +17,15 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.move
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.DialogWrapper
|
||||
import com.intellij.openapi.util.Comparing
|
||||
import com.intellij.openapi.util.Computable
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.refactoring.RefactoringBundle
|
||||
import com.intellij.refactoring.RefactoringSettings
|
||||
import com.intellij.refactoring.copy.CopyFilesOrDirectoriesHandler
|
||||
import com.intellij.refactoring.move.MoveCallback
|
||||
import com.intellij.refactoring.move.MoveHandler
|
||||
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesDialog
|
||||
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesProcessor
|
||||
@@ -40,12 +37,10 @@ import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||
import com.intellij.refactoring.util.MoveRenameUsageInfo
|
||||
import com.intellij.refactoring.util.NonCodeUsageInfo
|
||||
import com.intellij.usageView.UsageInfo
|
||||
import com.intellij.util.Function
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
|
||||
import org.jetbrains.kotlin.asJava.unwrapped
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind
|
||||
import org.jetbrains.kotlin.idea.JetFileType
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||
@@ -172,14 +167,14 @@ fun createMoveUsageInfoIfPossible(
|
||||
return MoveRenameUsageInfo(element, reference, startOffset, endOffset, referencedElement, false)
|
||||
}
|
||||
|
||||
public fun guessNewFileName(sourceFile: JetFile, declarationsToMove: Collection<JetNamedDeclaration>): String? {
|
||||
assert(sourceFile.getDeclarations().containsAll(declarationsToMove))
|
||||
|
||||
public fun guessNewFileName(declarationsToMove: Collection<JetNamedDeclaration>): String? {
|
||||
if (declarationsToMove.isEmpty()) return null
|
||||
if (sourceFile.getDeclarations().size() == declarationsToMove.size()) return sourceFile.getName()
|
||||
|
||||
val representative = declarationsToMove.firstOrNull { it is JetClassOrObject } ?: declarationsToMove.first()
|
||||
return "${representative.getName()}.${JetFileType.EXTENSION}"
|
||||
val representative = declarationsToMove.singleOrNull()
|
||||
?: declarationsToMove.filterIsInstance<JetClassOrObject>().singleOrNull()
|
||||
representative?.let { return "${it.name}.${JetFileType.EXTENSION}" }
|
||||
|
||||
return declarationsToMove.first().containingFile.name
|
||||
}
|
||||
|
||||
// returns true if successful
|
||||
|
||||
@@ -19,7 +19,6 @@ package org.jetbrains.kotlin.idea.refactoring.move
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import com.intellij.codeInsight.TargetElementUtilBase
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.EditorFactory
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
@@ -41,16 +40,18 @@ import org.jetbrains.kotlin.idea.core.refactoring.createKotlinFile
|
||||
import org.jetbrains.kotlin.idea.jsonUtils.getNullableString
|
||||
import org.jetbrains.kotlin.idea.jsonUtils.getString
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.changePackage.KotlinChangePackageRefactoring
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.DeferredJetFileKotlinMoveTarget
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.JetFileKotlinMoveTarget
|
||||
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.test.ConfigLibraryUtil
|
||||
import org.jetbrains.kotlin.idea.test.KotlinMultiFileTestCase
|
||||
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
||||
import org.jetbrains.kotlin.idea.search.allScope
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.JetFile
|
||||
import org.jetbrains.kotlin.psi.JetNamedDeclaration
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.test.JetTestUtils
|
||||
import java.io.File
|
||||
|
||||
@@ -248,8 +249,7 @@ enum class MoveAction {
|
||||
val moveTarget = config.getNullableString("targetPackage")?.let { packageName ->
|
||||
DeferredJetFileKotlinMoveTarget(project, FqName(packageName)) {
|
||||
val moveDestination = MultipleRootsMoveDestination(PackageWrapper(mainFile.getManager(), packageName))
|
||||
createKotlinFile(guessNewFileName(mainFile as JetFile, listOf(elementToMove))!!,
|
||||
moveDestination.getTargetDirectory(mainFile))
|
||||
createKotlinFile(guessNewFileName(listOf(elementToMove))!!, moveDestination.getTargetDirectory(mainFile))
|
||||
}
|
||||
} ?: config.getString("targetFile").let { filePath ->
|
||||
JetFileKotlinMoveTarget(PsiManager.getInstance(project).findFile(rootDir.findFileByRelativePath(filePath)!!) as JetFile)
|
||||
|
||||
Reference in New Issue
Block a user