diff --git a/annotations/com/intellij/refactoring/move/annotations.xml b/annotations/com/intellij/refactoring/move/annotations.xml new file mode 100644 index 00000000000..cedcd6882be --- /dev/null +++ b/annotations/com/intellij/refactoring/move/annotations.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 4237ad49b71..91551ddd72f 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -170,7 +170,13 @@ - + + diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties index 4da8c73edea..e7618766d8e 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties @@ -19,5 +19,9 @@ parameter.name.is.invalid=Parameter name ''{0}'' is invalid parameter.type.is.invalid=Parameter type ''{0}'' is invalid 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 + 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 5a710edf8c6..f9945bd3ef5 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt @@ -28,15 +28,18 @@ import com.intellij.psi.PsiElement import com.intellij.openapi.util.Key import com.intellij.psi.PsiDirectory import org.jetbrains.jet.lang.psi.JetFile -import com.intellij.psi.PsiFileFactory -import org.jetbrains.jet.lang.psi.psiUtil.getPackage -import org.jetbrains.jet.plugin.JetFileType +import com.intellij.openapi.roots.JavaProjectRootsUtil import com.intellij.psi.PsiPackage import com.intellij.psi.PsiClass import com.intellij.psi.PsiMember -import org.jetbrains.jet.lang.psi.JetNamedDeclaration import org.jetbrains.jet.lang.psi.JetPsiUtil import org.jetbrains.jet.asJava.namedUnwrappedElement +import org.jetbrains.jet.lang.psi.JetNamedDeclaration +import com.intellij.psi.util.PsiTreeUtil +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 /** * Replace [[JetSimpleNameExpression]] (and its enclosing qualifier) with qualified element given by FqName @@ -102,4 +105,7 @@ public fun PsiElement.getUsageContext(): PsiElement { is JetElement -> PsiTreeUtil.getParentOfType(this, javaClass(), javaClass())!! else -> ConflictsUtil.getContainer(this) } -} \ No newline at end of file +} + +public fun PsiElement.isInJavaSourceRoot(): Boolean = + !JavaProjectRootsUtil.isOutsideJavaSourceRoot(getContainingFile()) \ No newline at end of file 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 new file mode 100644 index 00000000000..839e732c41a --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/move/moveTopLevelDeclarations/MoveKotlinTopLevelDeclarationsHandler.kt @@ -0,0 +1,111 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.plugin.refactoring.move.moveTopLevelDeclarations + +import com.intellij.refactoring.move.MoveHandlerDelegate +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.refactoring.move.MoveCallback +import com.intellij.psi.PsiReference +import com.intellij.openapi.editor.Editor +import org.jetbrains.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 +import org.jetbrains.jet.lang.psi.JetClass +import com.intellij.psi.PsiPackage +import com.intellij.psi.PsiDirectory +import org.jetbrains.jet.lang.psi.psiUtil.getPackage +import org.jetbrains.jet.plugin.refactoring.move.getFileNameAfterMove +import java.util.HashSet +import com.intellij.refactoring.RefactoringBundle +import com.intellij.refactoring.util.CommonRefactoringUtil +import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesImpl +import org.jetbrains.jet.plugin.refactoring.move.moveTopLevelDeclarations.ui.MoveKotlinTopLevelDeclarationsDialog +import com.intellij.refactoring.JavaRefactoringSettings +import com.intellij.openapi.actionSystem.LangDataKeys +import org.jetbrains.jet.lang.psi.JetNamedFunction +import org.jetbrains.jet.lang.psi.JetProperty +import org.jetbrains.jet.lang.psi.JetNamedDeclaration + +public class MoveKotlinTopLevelDeclarationsHandler : MoveHandlerDelegate() { + private fun doMoveWithCheck( + project: Project, elements: Array, targetContainer: PsiElement?, callback: MoveCallback? + ): Boolean { + fun checkNameConflicts(): Boolean { + val fileNames = HashSet() + for (element in elements) { + if (element !is JetNamedDeclaration) continue + + val fileName = element.getFileNameAfterMove() + if (fileName != null && !fileNames.add(fileName)) { + val message = RefactoringBundle.getCannotRefactorMessage( + RefactoringBundle.message("there.are.going.to.be.multiple.destination.files.with.the.same.name") + ) + CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("move.title"), message, null, project) + return false + } + } + + return true + } + + if (!checkNameConflicts()) return false + if (!CommonRefactoringUtil.checkReadOnlyStatusRecursively(project, elements.toList(), true)) return false + + [suppress("UNCHECKED_CAST")] + val elementsToSearch = elements.toList() as List + val targetPackageName = MoveClassesOrPackagesImpl.getInitialTargetPackageName(targetContainer, elements) + val targetDirectory = MoveClassesOrPackagesImpl.getInitialTargetDirectory(targetContainer, elements) + val searchInComments = JavaRefactoringSettings.getInstance()!!.MOVE_SEARCH_IN_COMMENTS + val searchInText = JavaRefactoringSettings.getInstance()!!.MOVE_SEARCH_FOR_TEXT + + MoveKotlinTopLevelDeclarationsDialog( + project, elementsToSearch, targetPackageName, targetDirectory, searchInComments, searchInText, callback + ).show() + + return true + } + + override fun canMove(elements: Array, targetContainer: PsiElement?): Boolean { + return super.canMove(elements, targetContainer) && elements.all { e -> + if (e is JetClass || (e is JetObjectDeclaration && !e.isObjectLiteral()) || e is JetNamedFunction || e is JetProperty) { + val parent = e.getParent() + parent is JetFile && parent.isInJavaSourceRoot() + } + else false + } + } + + override fun isValidTarget(psiElement: PsiElement?, sources: Array): Boolean { + return psiElement is PsiPackage || (psiElement is PsiDirectory && psiElement.getPackage() != null) + } + + override fun doMove(project: Project, elements: Array, targetContainer: PsiElement?, callback: MoveCallback?) { + doMoveWithCheck(project, elements, targetContainer, callback) + } + + override fun tryToMove( + element: PsiElement, project: Project, dataContext: DataContext?, reference: PsiReference?, editor: Editor? + ): Boolean { + val elementsToMove = array(element) + val targetContainer = dataContext?.let { dataContext -> LangDataKeys.TARGET_PSI_ELEMENT.getData(dataContext) } + + return canMove(elementsToMove, targetContainer) && doMoveWithCheck(project, elementsToMove, targetContainer, null) + } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..ed7458f63e9 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.form @@ -0,0 +1,103 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 new file mode 100644 index 00000000000..4347fd21a68 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/move/moveTopLevelDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.java @@ -0,0 +1,250 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +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.options.ConfigurationException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.JavaProjectRootsUtil; +import com.intellij.openapi.ui.Messages; +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.refactoring.JavaRefactoringSettings; +import com.intellij.refactoring.MoveDestination; +import com.intellij.refactoring.PackageWrapper; +import com.intellij.refactoring.RefactoringBundle; +import com.intellij.refactoring.move.MoveCallback; +import com.intellij.refactoring.move.MoveHandler; +import com.intellij.refactoring.move.moveClassesOrPackages.DestinationFolderComboBox; +import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo; +import com.intellij.refactoring.ui.RefactoringDialog; +import com.intellij.refactoring.util.CommonRefactoringUtil; +import com.intellij.ui.ComboboxWithBrowseButton; +import com.intellij.ui.RecentsManager; +import com.intellij.ui.ReferenceEditorComboWithBrowseButton; +import com.intellij.usageView.UsageViewUtil; +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.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 javax.swing.*; +import java.util.List; + +public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { + private static final String RECENTS_KEY = "MoveKotlinTopLevelDeclarationsDialog.RECENTS_KEY"; + + private JLabel elementDescription; + private JCheckBox cbSearchInComments; + private JCheckBox cbSearchTextOccurences; + private JPanel mainPanel; + private ReferenceEditorComboWithBrowseButton classPackageChooser; + private ComboboxWithBrowseButton destinationFolderCB; + private JPanel targetPanel; + private JLabel targetDestinationLabel; + + private final List elementsToMove; + private final MoveCallback moveCallback; + + public MoveKotlinTopLevelDeclarationsDialog( + @NotNull Project project, + @NotNull List elementsToMove, + @Nullable String targetPackageName, + @Nullable PsiDirectory targetDirectory, + boolean searchInComments, + boolean searchForTextOccurences, + @Nullable MoveCallback moveCallback + ) { + super(project, true); + + this.elementsToMove = elementsToMove; + this.moveCallback = moveCallback; + + init(); + + setTitle(MoveHandler.REFACTORING_NAME); + + if (elementsToMove.size() == 1) { + PsiElement element = elementsToMove.get(0); + elementDescription.setText( + JetRefactoringBundle.message( + "refactoring.move.specifc.element", + UsageViewUtil.getType(element), + UsageViewUtil.getLongName(element) + ) + ); + } + 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() { + classPackageChooser = createPackageChooser(); + + destinationFolderCB = new DestinationFolderComboBox() { + @Override + public String getTargetPackage() { + return MoveKotlinTopLevelDeclarationsDialog.this.getTargetPackage(); + } + }; + } + + private ReferenceEditorComboWithBrowseButton createPackageChooser() { + ReferenceEditorComboWithBrowseButton packageChooser = + new PackageNameReferenceEditorCombo("", myProject, RECENTS_KEY, RefactoringBundle.message("choose.destination.package")); + Document document = packageChooser.getChildComponent().getDocument(); + document.addDocumentListener(new DocumentAdapter() { + @Override + public void documentChanged(DocumentEvent e) { + validateButtons(); + } + }); + + return packageChooser; + } + + private boolean hasAnySourceRoots() { + return !JavaProjectRootsUtil.getSuitableDestinationSourceRoots(myProject).isEmpty(); + } + + @Override + protected JComponent createCenterPanel() { + boolean isDestinationVisible = hasAnySourceRoots(); + destinationFolderCB.setVisible(isDestinationVisible); + targetDestinationLabel.setVisible(isDestinationVisible); + return null; + } + + @Override + protected JComponent createNorthPanel() { + return mainPanel; + } + + @Override + protected String getDimensionServiceKey() { + return "#com.intellij.refactoring.move.moveClassesOrPackages.MoveKotlinTopLevelDeclarationsDialog.classes"; + } + + @Override + public JComponent getPreferredFocusedComponent() { + return classPackageChooser.getChildComponent(); + } + + 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); + } + + @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"); + } + } + + protected String getTargetPackage() { + return classPackageChooser.getText(); + } + + @Override + protected void doAction() { + MoveDestination destination = selectDestination(); + if (destination == null) return; + + saveRefactoringSettings(); + for (PsiElement element : elementsToMove) { + String message = destination.verify(element.getContainingFile()); + if (message != null) { + CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), message, null, myProject); + return; + } + } + + try { + MoveKotlinTopLevelDeclarationsOptions options = new MoveKotlinTopLevelDeclarationsOptions( + elementsToMove, destination, isSearchInComments(), isSearchInNonJavaFiles(), moveCallback + ); + invokeRefactoring(new MoveKotlinTopLevelDeclarationsProcessor(myProject, options)); + } + catch (IncorrectOperationException e) { + CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), e.getMessage(), null, myProject); + } + } + + protected final boolean isSearchInNonJavaFiles() { + return cbSearchTextOccurences.isSelected(); + } + + protected final boolean isSearchInComments() { + return cbSearchInComments.isSelected(); + } +} \ No newline at end of file