diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties index e7618766d8e..0d29629b52e 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties @@ -22,6 +22,8 @@ column.name.val.var=Val/Var refactoring.move.top.level.declarations=Move top-level declarations refactoring.move.specifc.element=Move {0} {1} refactoring.move.selected.elements=Move selected elements +refactoring.move.top.level.declaration.file.title=Choose Destination File +refactoring.move.non.kotlin.file=Target must be a Kotlin file package.private.0.will.no.longer.be.accessible.from.1=Package-private {0} will no longer be accessible from {1} 0.uses.package.private.1={0} uses package-private {1} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt index f9945bd3ef5..e78905f321a 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt @@ -40,6 +40,12 @@ import com.intellij.refactoring.util.ConflictsUtil import org.jetbrains.jet.lang.psi.psiUtil.getPackage import com.intellij.psi.PsiFileFactory import org.jetbrains.jet.plugin.JetFileType +import com.intellij.openapi.project.Project +import java.io.File +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.psi.PsiManager +import com.intellij.psi.PsiFile +import com.intellij.openapi.vfs.VirtualFile /** * Replace [[JetSimpleNameExpression]] (and its enclosing qualifier) with qualified element given by FqName @@ -83,6 +89,12 @@ fun createKotlinFile(fileName: String, targetDir: PsiDirectory): JetFile { return targetDir.add(file) as JetFile } +public fun File.toVirtualFile(): VirtualFile? = LocalFileSystem.getInstance()!!.findFileByIoFile(this) + +public fun File.toPsiFile(project: Project): PsiFile? { + return toVirtualFile()?.let { vfile -> PsiManager.getInstance(project).findFile(vfile) } +} + /** * Returns FqName for given declaration (either Java or Kotlin) */ diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/move/moveTopLevelDeclarations/MoveKotlinTopLevelDeclarationsHandler.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/move/moveTopLevelDeclarations/MoveKotlinTopLevelDeclarationsHandler.kt index 839e732c41a..efa79aa6d61 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/move/moveTopLevelDeclarations/MoveKotlinTopLevelDeclarationsHandler.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/move/moveTopLevelDeclarations/MoveKotlinTopLevelDeclarationsHandler.kt @@ -23,7 +23,6 @@ import com.intellij.psi.PsiElement import com.intellij.refactoring.move.MoveCallback import com.intellij.psi.PsiReference import com.intellij.openapi.editor.Editor -import org.jetbrains.jet.lang.psi.JetClassOrObject import org.jetbrains.jet.lang.psi.JetFile import org.jetbrains.jet.plugin.refactoring.isInJavaSourceRoot import org.jetbrains.jet.lang.psi.JetObjectDeclaration @@ -74,9 +73,17 @@ public class MoveKotlinTopLevelDeclarationsHandler : MoveHandlerDelegate() { val targetDirectory = MoveClassesOrPackagesImpl.getInitialTargetDirectory(targetContainer, elements) val searchInComments = JavaRefactoringSettings.getInstance()!!.MOVE_SEARCH_IN_COMMENTS val searchInText = JavaRefactoringSettings.getInstance()!!.MOVE_SEARCH_FOR_TEXT + val targetFile: JetFile? = when (targetContainer) { + is JetFile -> targetContainer + else -> { + val files = elements.mapTo(HashSet()) { e -> e.getContainingFile() as JetFile } + if (files.size == 1) files.first() else null + } + } + val moveToPackage = targetContainer !is JetFile MoveKotlinTopLevelDeclarationsDialog( - project, elementsToSearch, targetPackageName, targetDirectory, searchInComments, searchInText, callback + project, elementsToSearch, targetPackageName, targetDirectory, targetFile, moveToPackage, searchInComments, searchInText, callback ).show() return true @@ -93,7 +100,7 @@ public class MoveKotlinTopLevelDeclarationsHandler : MoveHandlerDelegate() { } override fun isValidTarget(psiElement: PsiElement?, sources: Array): Boolean { - return psiElement is PsiPackage || (psiElement is PsiDirectory && psiElement.getPackage() != null) + return psiElement is PsiPackage || (psiElement is PsiDirectory && psiElement.getPackage() != null) || psiElement is JetFile } override fun doMove(project: Project, elements: Array, targetContainer: PsiElement?, callback: MoveCallback?) { diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.form b/idea/src/org/jetbrains/jet/plugin/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.form index ed7458f63e9..7c6bab4dae7 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.form +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.form @@ -68,7 +68,7 @@ - + @@ -82,7 +82,7 @@ - + @@ -90,14 +90,28 @@ + + + + + + + + + + + + + + - - + + diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.java b/idea/src/org/jetbrains/jet/plugin/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.java index 4347fd21a68..0b182eb4629 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.java +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.java @@ -18,15 +18,17 @@ package org.jetbrains.jet.plugin.refactoring.move.moveTopLevelDeclarations.ui; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.JavaProjectRootsUtil; +import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.TextComponentAccessor; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.util.Pass; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiManager; -import com.intellij.psi.PsiNameHelper; +import com.intellij.psi.*; import com.intellij.refactoring.JavaRefactoringSettings; import com.intellij.refactoring.MoveDestination; import com.intellij.refactoring.PackageWrapper; @@ -45,12 +47,16 @@ import com.intellij.util.IncorrectOperationException; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.psi.JetFile; import org.jetbrains.jet.lang.psi.JetNamedDeclaration; import org.jetbrains.jet.plugin.refactoring.JetRefactoringBundle; -import org.jetbrains.jet.plugin.refactoring.move.moveTopLevelDeclarations.MoveKotlinTopLevelDeclarationsOptions; -import org.jetbrains.jet.plugin.refactoring.move.moveTopLevelDeclarations.MoveKotlinTopLevelDeclarationsProcessor; +import org.jetbrains.jet.plugin.refactoring.RefactoringPackage; +import org.jetbrains.jet.plugin.refactoring.move.moveTopLevelDeclarations.*; import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; import java.util.List; public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { @@ -64,6 +70,9 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { private ComboboxWithBrowseButton destinationFolderCB; private JPanel targetPanel; private JLabel targetDestinationLabel; + private JRadioButton rbMoveToPackage; + private JRadioButton rbMoveToFile; + private TextFieldWithBrowseButton fileChooser; private final List elementsToMove; private final MoveCallback moveCallback; @@ -73,6 +82,8 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { @NotNull List elementsToMove, @Nullable String targetPackageName, @Nullable PsiDirectory targetDirectory, + @Nullable JetFile targetFile, + boolean moveToPackage, boolean searchInComments, boolean searchForTextOccurences, @Nullable MoveCallback moveCallback @@ -86,6 +97,84 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { setTitle(MoveHandler.REFACTORING_NAME); + initElementDescription(elementsToMove); + + initSearchOptions(searchInComments, searchForTextOccurences); + + initPackageChooser(targetPackageName, targetDirectory); + + initFileChooser(targetFile); + + initMoveToButtons(moveToPackage); + + updateControls(); + } + + private void initPackageChooser(String targetPackageName, PsiDirectory targetDirectory) { + if (targetPackageName != null && targetPackageName.length() != 0) { + classPackageChooser.prependItem(targetPackageName); + } + + ((DestinationFolderComboBox) destinationFolderCB).setData( + myProject, + targetDirectory, + new Pass() { + @Override + public void pass(String s) { + setErrorText(s); + } + }, + classPackageChooser.getChildComponent() + ); + } + + private void initSearchOptions(boolean searchInComments, boolean searchForTextOccurences) { + cbSearchInComments.setSelected(searchInComments); + cbSearchTextOccurences.setSelected(searchForTextOccurences); + } + + private void initMoveToButtons(boolean moveToPackage) { + if (moveToPackage) { + rbMoveToPackage.setSelected(true); + } + else { + rbMoveToFile.setSelected(true); + } + + rbMoveToPackage.addActionListener( + new ActionListener() { + @Override + public void actionPerformed(@NotNull ActionEvent e) { + classPackageChooser.requestFocus(); + updateControls(); + } + } + ); + + rbMoveToFile.addActionListener( + new ActionListener() { + @Override + public void actionPerformed(@NotNull ActionEvent e) { + fileChooser.requestFocus(); + updateControls(); + } + } + ); + } + + private void initFileChooser(JetFile targetFile) { + FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor(); + descriptor.setRoots(ProjectRootManager.getInstance(myProject).getContentRoots()); + descriptor.setIsTreeRootVisible(true); + + String title = JetRefactoringBundle.message("refactoring.move.top.level.declaration.file.title"); + fileChooser.addBrowseFolderListener(title, null, myProject, descriptor, TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT); + if (targetFile != null) { + fileChooser.setText(targetFile.getVirtualFile().getPath()); + } + } + + private void initElementDescription(List elementsToMove) { if (elementsToMove.size() == 1) { PsiElement element = elementsToMove.get(0); elementDescription.setText( @@ -99,28 +188,6 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { else if (elementsToMove.size() > 1) { elementDescription.setText(JetRefactoringBundle.message("refactoring.move.selected.elements")); } - - if (targetPackageName != null && targetPackageName.length() != 0) { - classPackageChooser.prependItem(targetPackageName); - } - - cbSearchInComments.setSelected(searchInComments); - cbSearchTextOccurences.setSelected(searchForTextOccurences); - - ((DestinationFolderComboBox) destinationFolderCB).setData( - myProject, - targetDirectory, - new Pass() { - @Override - public void pass(String s) { - setErrorText(s); - } - }, - classPackageChooser.getChildComponent() - ); - - UIUtil.setEnabled(targetPanel, hasAnySourceRoots(), true); - validateButtons(); } private void createUIComponents() { @@ -148,10 +215,74 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { return packageChooser; } + private void updateControls() { + boolean moveToPackage = isMoveToPackage(); + classPackageChooser.setEnabled(moveToPackage); + fileChooser.setEnabled(!moveToPackage); + UIUtil.setEnabled(targetPanel, moveToPackage && hasAnySourceRoots(), true); + validateButtons(); + } + private boolean hasAnySourceRoots() { return !JavaProjectRootsUtil.getSuitableDestinationSourceRoots(myProject).isEmpty(); } + private void saveRefactoringSettings() { + JavaRefactoringSettings refactoringSettings = JavaRefactoringSettings.getInstance(); + refactoringSettings.MOVE_SEARCH_IN_COMMENTS = isSearchInComments(); + refactoringSettings.MOVE_SEARCH_FOR_TEXT = isSearchInNonJavaFiles(); + refactoringSettings.MOVE_PREVIEW_USAGES = isPreviewUsages(); + } + + @Nullable + private KotlinMoveTarget selectMoveTarget() { + String message = verifyBeforeRun(); + if (message != null) { + setErrorText(message); + return null; + } + + setErrorText(null); + + if (isMoveToPackage()) { + String packageName = getTargetPackage().trim(); + + RecentsManager.getInstance(myProject).registerRecentEntry(RECENTS_KEY, packageName); + PackageWrapper targetPackage = new PackageWrapper(PsiManager.getInstance(myProject), packageName); + if (!targetPackage.exists()) { + int ret = Messages.showYesNoDialog(myProject, RefactoringBundle.message("package.does.not.exist", packageName), + RefactoringBundle.message("move.title"), Messages.getQuestionIcon()); + if (ret != Messages.YES) return null; + } + + MoveDestination moveDestination = ((DestinationFolderComboBox) destinationFolderCB).selectDirectory(targetPackage, false); + return moveDestination != null ? new MoveDestinationKotlinMoveTarget(moveDestination) : null; + } + + JetFile jetFile = (JetFile) RefactoringPackage.toPsiFile(new File(getTargetFilePath()), myProject); + assert jetFile != null : "Non-Kotlin files must be filtered out before starting the refactoring"; + + return new JetFileKotlinMoveTarget(jetFile); + } + + @Nullable + private String verifyBeforeRun() { + if (isMoveToPackage()) { + String name = getTargetPackage().trim(); + if (name.length() != 0 && !PsiNameHelper.getInstance(myProject).isQualifiedName(name)) { + return "\'" + name + "\' is invalid destination package name"; + } + } + else { + PsiFile targetFile = RefactoringPackage.toPsiFile(new File(getTargetFilePath()), myProject); + if (!(targetFile instanceof JetFile)) { + return JetRefactoringBundle.message("refactoring.move.non.kotlin.file"); + } + } + + return null; + } + @Override protected JComponent createCenterPanel() { boolean isDestinationVisible = hasAnySourceRoots(); @@ -170,59 +301,30 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { return "#com.intellij.refactoring.move.moveClassesOrPackages.MoveKotlinTopLevelDeclarationsDialog.classes"; } - @Override - public JComponent getPreferredFocusedComponent() { - return classPackageChooser.getChildComponent(); + protected final String getTargetPackage() { + return classPackageChooser.getText(); } - private void saveRefactoringSettings() { - JavaRefactoringSettings refactoringSettings = JavaRefactoringSettings.getInstance(); - refactoringSettings.MOVE_SEARCH_IN_COMMENTS = isSearchInComments(); - refactoringSettings.MOVE_SEARCH_FOR_TEXT = isSearchInNonJavaFiles(); - refactoringSettings.MOVE_PREVIEW_USAGES = isPreviewUsages(); - } - - @Nullable - private MoveDestination selectDestination() { - String packageName = getTargetPackage().trim(); - - if (packageName.length() > 0 && !PsiNameHelper.getInstance(myProject).isQualifiedName(packageName)) { - Messages.showErrorDialog(myProject, RefactoringBundle.message("please.enter.a.valid.target.package.name"), - RefactoringBundle.message("move.title")); - return null; - } - - RecentsManager.getInstance(myProject).registerRecentEntry(RECENTS_KEY, packageName); - PackageWrapper targetPackage = new PackageWrapper(PsiManager.getInstance(myProject), packageName); - if (!targetPackage.exists()) { - int ret = Messages.showYesNoDialog(myProject, RefactoringBundle.message("package.does.not.exist", packageName), - RefactoringBundle.message("move.title"), Messages.getQuestionIcon()); - if (ret != Messages.YES) return null; - } - - return ((DestinationFolderComboBox) destinationFolderCB).selectDirectory(targetPackage, false); + protected final String getTargetFilePath() { + return fileChooser.getText(); } @Override protected void canRun() throws ConfigurationException { - String name = getTargetPackage().trim(); - if (name.length() != 0 && !PsiNameHelper.getInstance(myProject).isQualifiedName(name)) { - throw new ConfigurationException("\'" + name + "\' is invalid destination package name"); + String message = verifyBeforeRun(); + if (message != null) { + throw new ConfigurationException(message); } } - protected String getTargetPackage() { - return classPackageChooser.getText(); - } - @Override protected void doAction() { - MoveDestination destination = selectDestination(); - if (destination == null) return; + KotlinMoveTarget target = selectMoveTarget(); + if (target == null) return; saveRefactoringSettings(); for (PsiElement element : elementsToMove) { - String message = destination.verify(element.getContainingFile()); + String message = target.verify(element.getContainingFile()); if (message != null) { CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), message, null, myProject); return; @@ -231,7 +333,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { try { MoveKotlinTopLevelDeclarationsOptions options = new MoveKotlinTopLevelDeclarationsOptions( - elementsToMove, destination, isSearchInComments(), isSearchInNonJavaFiles(), moveCallback + elementsToMove, target, isSearchInComments(), isSearchInNonJavaFiles(), moveCallback ); invokeRefactoring(new MoveKotlinTopLevelDeclarationsProcessor(myProject, options)); } @@ -240,11 +342,20 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { } } - protected final boolean isSearchInNonJavaFiles() { + private boolean isSearchInNonJavaFiles() { return cbSearchTextOccurences.isSelected(); } - protected final boolean isSearchInComments() { + private boolean isSearchInComments() { return cbSearchInComments.isSelected(); } + + private boolean isMoveToPackage() { + return rbMoveToPackage.isSelected(); + } + + @Override + public JComponent getPreferredFocusedComponent() { + return classPackageChooser.getChildComponent(); + } } \ No newline at end of file