From fa03e4e76c8783845b589faf2ccd958b29e20e50 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Mon, 18 May 2015 14:13:57 +0300 Subject: [PATCH] Move: Implement "Update package directive" option --- idea/src/META-INF/plugin.xml | 3 +- .../KotlinMoveFilesOrDirectoriesHandler.kt | 13 ++- .../MoveKotlinFileHandler.kt | 6 +- .../MoveKotlinTopLevelDeclarationsHandler.kt | 7 +- ...lesOrDirectoriesDialogWithKotlinOptions.kt | 90 +++++++++++++++++++ .../kotlin/idea/refactoring/move/moveUtils.kt | 89 ++++++++++++++++++ 6 files changed, 200 insertions(+), 8 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/MoveFilesOrDirectoriesDialogWithKotlinOptions.kt diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 7f870aa28e0..673f6ef984a 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -251,7 +251,8 @@ + implementation="org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories.KotlinMoveFilesOrDirectoriesHandler" + order="before moveJavaFileOrDir"/> , targetElement: PsiElement?): Array { + override fun adjustForMove(project: Project, sourceElements: Array, targetElement: PsiElement?): Array { return sourceElements.map { if (it is JetClassOrObject) it.getContainingFile() else it }.toTypedArray() } + override fun doMove(project: Project, elements: Array, targetContainer: PsiElement?, callback: MoveCallback?) { + if (!(targetContainer == null || targetContainer is PsiDirectory || targetContainer is PsiDirectoryContainer)) return + + moveFilesOrDirectories(project, adjustForMove(project, elements, targetContainer), targetContainer) { + callback?.refactoringCompleted() + } + } + companion object { public fun isMovableClass(clazz: JetClassOrObject): Boolean { return clazz.isTopLevel() && clazz.getContainingJetFile().getDeclarations().all { it !is JetClassOrObject || it == clazz } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/MoveKotlinFileHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/MoveKotlinFileHandler.kt index 42ee40a5fcf..18aa9ac3a75 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/MoveKotlinFileHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/MoveKotlinFileHandler.kt @@ -32,6 +32,7 @@ import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.MoveK import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.MoveKotlinTopLevelDeclarationsProcessor import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.Mover import org.jetbrains.kotlin.idea.refactoring.move.postProcessMoveUsages +import org.jetbrains.kotlin.idea.refactoring.move.updatePackageDirective import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.JetNamedDeclaration @@ -60,11 +61,12 @@ public class MoveKotlinFileHandler : MoveFileHandler() { ): List? { clearState() - if (psiFile !is JetFile || !psiFile.packageMatchesDirectory()) return null + if (psiFile !is JetFile || !(psiFile.updatePackageDirective ?: psiFile.packageMatchesDirectory())) return null val newPackage = newParent.getPackage() ?: return null val packageNameInfo = PackageNameInfo(psiFile.getPackageFqName(), FqName(newPackage.getQualifiedName())) + if (packageNameInfo.oldPackageName == packageNameInfo.newPackageName) return null val project = psiFile.getProject() val declarationMoveProcessor = MoveKotlinTopLevelDeclarationsProcessor( @@ -93,6 +95,8 @@ public class MoveKotlinFileHandler : MoveFileHandler() { override fun updateMovedFile(file: PsiFile) { if (file !is JetFile) return + + file.updatePackageDirective = null val packageNameInfo = packageNameInfo ?: return val internalUsages = file.getInternalReferencesToUpdateOnPackageNameChange(packageNameInfo) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/MoveKotlinTopLevelDeclarationsHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/MoveKotlinTopLevelDeclarationsHandler.kt index cb0dcf64356..5c2b340b608 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/MoveKotlinTopLevelDeclarationsHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/MoveKotlinTopLevelDeclarationsHandler.kt @@ -32,6 +32,7 @@ import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackages import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil import com.intellij.refactoring.util.CommonRefactoringUtil import org.jetbrains.kotlin.idea.core.refactoring.isInJavaSourceRoot +import org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.ui.MoveKotlinTopLevelDeclarationsDialog import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getPackage @@ -51,11 +52,7 @@ public class MoveKotlinTopLevelDeclarationsHandler : MoveHandlerDelegate() { val sourceFile = sourceFiles.singleOrNull() if (sourceFile == null) { if (sourceFiles.all { it.getDeclarations().size() == 1 }) { - MoveFilesOrDirectoriesUtil.doMove(project, - sourceFiles.toTypedArray(), - arrayOf(targetContainer), - null) - + moveFilesOrDirectories(project, sourceFiles.toTypedArray(), targetContainer) return true } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/MoveFilesOrDirectoriesDialogWithKotlinOptions.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/MoveFilesOrDirectoriesDialogWithKotlinOptions.kt new file mode 100644 index 00000000000..8999fd30902 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/MoveFilesOrDirectoriesDialogWithKotlinOptions.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2010-2015 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.kotlin.idea.refactoring.move.moveTopLevelDeclarations.ui + +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiElement +import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesDialog +import com.intellij.ui.NonFocusableCheckBox +import com.intellij.util.ui.UIUtil +import org.jetbrains.kotlin.psi.JetFile +import org.jetbrains.kotlin.psi.psiUtil.packageMatchesDirectory +import java.awt.GridBagConstraints +import java.awt.Insets +import javax.swing.JCheckBox +import javax.swing.JComponent +import javax.swing.JLabel +import kotlin.properties.Delegates + +public class MoveFilesOrDirectoriesDialogWithKotlinOptions( + project: Project, + callback: (MoveFilesOrDirectoriesDialog?) -> Unit): MoveFilesOrDirectoriesDialog(project, callback) { + private var cbUpdatePackageDirective: JCheckBox? = null + + public val updatePackageDirective: Boolean + get() = cbUpdatePackageDirective!!.isSelected() + + override fun createNorthPanel(): JComponent { + val panel = super.createNorthPanel() + + val gbc = GridBagConstraints() + + gbc.gridwidth = 1 + gbc.gridx = 0 + gbc.gridy = GridBagConstraints.RELATIVE + gbc.weightx = 0.0 + gbc.weighty = 0.0 + gbc.fill = GridBagConstraints.NONE + gbc.anchor = GridBagConstraints.WEST + gbc.insets = Insets(UIUtil.LARGE_VGAP, 0, 0, UIUtil.DEFAULT_HGAP) + panel.add(JLabel(), gbc) + + cbUpdatePackageDirective = NonFocusableCheckBox() + gbc.gridx = 1 + gbc.weightx = 1.0 + gbc.weighty = 0.0 + gbc.fill = GridBagConstraints.HORIZONTAL + gbc.anchor = GridBagConstraints.WEST + gbc.insets = Insets(UIUtil.LARGE_VGAP, 0, 0, 0) + panel.add(cbUpdatePackageDirective, gbc) + + return panel + } + + override fun setData(psiElements: Array, initialTargetDirectory: PsiDirectory?, helpID: String) { + super.setData(psiElements, initialTargetDirectory, helpID) + + with (cbUpdatePackageDirective!!) { + val jetFiles = psiElements.filterIsInstance() + if (jetFiles.isEmpty()) { + getParent().remove(cbUpdatePackageDirective) + return + } + + val singleFile = jetFiles.singleOrNull() + if (singleFile != null) { + setSelected(singleFile.packageMatchesDirectory()) + setText("Update package directive") + } + else { + setSelected(true) + setText("Update package directive for Kotlin files") + } + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt index 8acea9f02ce..9d108e8b931 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt @@ -16,15 +16,31 @@ 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 +import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil import com.intellij.refactoring.move.moveMembers.MockMoveMembersOptions import com.intellij.refactoring.move.moveMembers.MoveMemberHandler import com.intellij.refactoring.move.moveMembers.MoveMembersProcessor +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 @@ -35,10 +51,13 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.codeInsight.JetFileReferencesResolver import org.jetbrains.kotlin.idea.refactoring.fqName.isImported +import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.ui.MoveFilesOrDirectoriesDialogWithKotlinOptions import org.jetbrains.kotlin.idea.references.JetReference import org.jetbrains.kotlin.idea.references.JetSimpleNameReference import org.jetbrains.kotlin.idea.references.JetSimpleNameReference.ShorteningMode import org.jetbrains.kotlin.idea.util.ImportInsertHelper +import org.jetbrains.kotlin.idea.util.application.executeCommand +import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* @@ -261,3 +280,73 @@ fun postProcessMoveUsages(usages: List, return nonCodeUsages } + +var JetFile.updatePackageDirective: Boolean? by UserDataProperty(Key.create("UPDATE_PACKAGE_DIRECTIVE")) + +// Mostly copied from MoveFilesOrDirectoriesUtil.doMove() +public fun moveFilesOrDirectories( + project: Project, + elements: Array, + targetElement: PsiElement?, + moveCallback: (() -> Unit)? = null +) { + elements.forEach { if (it !is PsiFile && it !is PsiDirectory) throw IllegalArgumentException("unexpected element type: " + it) } + + val targetDirectory = MoveFilesOrDirectoriesUtil.resolveToDirectory(project, targetElement) + if (targetElement != null && targetDirectory == null) return + + val initialTargetDirectory = MoveFilesOrDirectoriesUtil.getInitialTargetDirectory(targetDirectory, elements) + + fun doRun(moveDialog: MoveFilesOrDirectoriesDialog?) { + fun closeDialog() { + moveDialog?.close(DialogWrapper.CANCEL_EXIT_CODE) + } + + project.executeCommand(MoveHandler.REFACTORING_NAME) { + val selectedDir = if (moveDialog != null) moveDialog.getTargetDirectory() else initialTargetDirectory + val updatePackageDirective = (moveDialog as? MoveFilesOrDirectoriesDialogWithKotlinOptions)?.updatePackageDirective + + try { + val choice = if (elements.size() > 1 || elements[0] is PsiDirectory) intArrayOf(-1) else null + val elementsToMove = elements.filterNot { + it is PsiFile + && runWriteAction { CopyFilesOrDirectoriesHandler.checkFileExist(selectedDir, choice, it, it.getName(), "Move") } + } + + elementsToMove.forEach { + MoveFilesOrDirectoriesUtil.checkMove(it, selectedDir) + (it as? JetFile)?.let { it.updatePackageDirective = updatePackageDirective } + } + + if (elementsToMove.isNotEmpty()) { + MoveFilesOrDirectoriesProcessor( + project, + elementsToMove.toTypedArray(), + selectedDir, + RefactoringSettings.getInstance().MOVE_SEARCH_FOR_REFERENCES_FOR_FILE, + false, + false, + moveCallback, + ::closeDialog + ).run() + } + else { + closeDialog() + } + } + catch (e: IncorrectOperationException) { + CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), e.getMessage(), "refactoring.moveFile", project) + } + } + } + + if (ApplicationManager.getApplication().isUnitTestMode()) { + doRun(null) + return + } + + with(MoveFilesOrDirectoriesDialogWithKotlinOptions(project, ::doRun)) { + setData(elements, initialTargetDirectory, "refactoring.moveFile") + show() + } +}