Refactor of Move Refactoring
The main purpose of this commit - to make this refactoring logic can be covered as much as possible. To make this possible we should to extract all possible UI code from business logic (and vice versa). Additional fixes was applied during refactoring process. There is come major steps made in this commit: 1) Extract business logic to separate models 2) Fixes of business models logic 3) Add "Delete empty source file" checkbox instead of modal message box 4) Improve error messaging in move refactoring dialogs 5) Inject interface into move handler that makes UI dialogs could be overrided 6) Inject flag into move handler that makes UI conflicts dialog could be overrided
This commit is contained in:
@@ -43,6 +43,7 @@ class KotlinRefactoringSettings : PersistentStateComponent<KotlinRefactoringSett
|
||||
@JvmField var MOVE_PREVIEW_USAGES = true
|
||||
@JvmField var MOVE_SEARCH_IN_COMMENTS = true
|
||||
@JvmField var MOVE_SEARCH_FOR_TEXT = true
|
||||
@JvmField var MOVE_DELETE_EMPTY_SOURCE_FILES = true
|
||||
|
||||
@JvmField var EXTRACT_INTERFACE_JAVADOC: Int = 0
|
||||
@JvmField var EXTRACT_SUPERCLASS_JAVADOC: Int = 0
|
||||
|
||||
+1
@@ -95,6 +95,7 @@ class ExtractDeclarationFromCurrentFileIntention :
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
MoveCallback {
|
||||
runBlocking {
|
||||
withTimeoutOrNull(TIMEOUT_FOR_IMPORT_OPTIMIZING_MS) {
|
||||
|
||||
+7
-1
@@ -29,7 +29,8 @@ class KotlinAwareMoveFilesOrDirectoriesProcessor @JvmOverloads constructor(
|
||||
searchInComments: Boolean,
|
||||
searchInNonJavaFiles: Boolean,
|
||||
moveCallback: MoveCallback?,
|
||||
prepareSuccessfulCallback: Runnable = EmptyRunnable.INSTANCE
|
||||
prepareSuccessfulCallback: Runnable = EmptyRunnable.INSTANCE,
|
||||
private val throwOnConflicts: Boolean = false
|
||||
) : MoveFilesOrDirectoriesProcessor(
|
||||
project,
|
||||
elementsToMove.toTypedArray(),
|
||||
@@ -58,6 +59,11 @@ class KotlinAwareMoveFilesOrDirectoriesProcessor @JvmOverloads constructor(
|
||||
return showConflicts(conflicts, usages)
|
||||
}
|
||||
|
||||
override fun showConflicts(conflicts: MultiMap<PsiElement, String>, usages: Array<out UsageInfo>?): Boolean {
|
||||
if (throwOnConflicts && !conflicts.isEmpty) throw RefactoringConflictsFoundException()
|
||||
return super.showConflicts(conflicts, usages)
|
||||
}
|
||||
|
||||
private fun markPsiFiles(mark: PsiFile.() -> Unit) {
|
||||
fun PsiElement.doMark(mark: PsiFile.() -> Unit) {
|
||||
when (this) {
|
||||
|
||||
+72
-19
@@ -31,7 +31,6 @@ import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||
import org.jetbrains.kotlin.asJava.unwrapped
|
||||
import org.jetbrains.kotlin.idea.core.getPackage
|
||||
import org.jetbrains.kotlin.idea.refactoring.canRefactor
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.invokeMoveFilesOrDirectoriesRefactoring
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.KotlinAwareMoveFilesOrDirectoriesDialog
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.KotlinSelectNestedClassRefactoringDialog
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.MoveKotlinNestedClassesDialog
|
||||
@@ -43,7 +42,60 @@ import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isTopLevelInFileOrScript
|
||||
import java.util.*
|
||||
|
||||
class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
|
||||
private val defaultHandlerActions = object : MoveKotlinDeclarationsHandlerActions {
|
||||
|
||||
override fun showErrorHint(project: Project, editor: Editor?, message: String, title: String, helpId: String?) {
|
||||
CommonRefactoringUtil.showErrorHint(project, editor, message, title, helpId)
|
||||
}
|
||||
|
||||
override fun invokeMoveKotlinNestedClassesRefactoring(
|
||||
project: Project,
|
||||
elementsToMove: List<KtClassOrObject>,
|
||||
originalClass: KtClassOrObject,
|
||||
targetClass: KtClassOrObject,
|
||||
moveCallback: MoveCallback?
|
||||
) = MoveKotlinNestedClassesDialog(project, elementsToMove, originalClass, targetClass, moveCallback).show()
|
||||
|
||||
override fun invokeMoveKotlinTopLevelDeclarationsRefactoring(
|
||||
project: Project,
|
||||
elementsToMove: Set<KtNamedDeclaration>,
|
||||
targetPackageName: String,
|
||||
targetDirectory: PsiDirectory?,
|
||||
targetFile: KtFile?,
|
||||
moveToPackage: Boolean,
|
||||
searchInComments: Boolean,
|
||||
searchForTextOccurrences: Boolean,
|
||||
deleteEmptySourceFiles: Boolean,
|
||||
moveCallback: MoveCallback?
|
||||
) = MoveKotlinTopLevelDeclarationsDialog(
|
||||
project,
|
||||
elementsToMove,
|
||||
targetPackageName,
|
||||
targetDirectory,
|
||||
targetFile,
|
||||
moveToPackage,
|
||||
searchInComments,
|
||||
searchForTextOccurrences,
|
||||
deleteEmptySourceFiles,
|
||||
moveCallback
|
||||
).show()
|
||||
|
||||
override fun invokeKotlinSelectNestedClassChooser(nestedClass: KtClassOrObject, targetContainer: PsiElement?) =
|
||||
KotlinSelectNestedClassRefactoringDialog.chooseNestedClassRefactoring(nestedClass, targetContainer)
|
||||
|
||||
override fun invokeKotlinAwareMoveFilesOrDirectoriesRefactoring(
|
||||
project: Project,
|
||||
initialDirectory: PsiDirectory?,
|
||||
elements: Array<out PsiElement>,
|
||||
moveCallback: MoveCallback?
|
||||
) = KotlinAwareMoveFilesOrDirectoriesDialog(project, initialDirectory, elements, moveCallback).show()
|
||||
}
|
||||
|
||||
class MoveKotlinDeclarationsHandler internal constructor(private val handlerActions: MoveKotlinDeclarationsHandlerActions) :
|
||||
MoveHandlerDelegate() {
|
||||
|
||||
constructor() : this(defaultHandlerActions)
|
||||
|
||||
private fun getUniqueContainer(elements: Array<out PsiElement>): PsiElement? {
|
||||
val allTopLevel = elements.all { isTopLevelInFileOrScript(it) }
|
||||
|
||||
@@ -72,7 +124,7 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
|
||||
|
||||
val container = getUniqueContainer(elements)
|
||||
if (container == null) {
|
||||
CommonRefactoringUtil.showErrorHint(
|
||||
handlerActions.showErrorHint(
|
||||
project, editor, "All declarations must belong to the same directory or class", MOVE_DECLARATIONS, null
|
||||
)
|
||||
return false
|
||||
@@ -89,20 +141,20 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
|
||||
// todo: allow moving companion object
|
||||
if (elementsToSearch.any { it is KtObjectDeclaration && it.isCompanion() }) {
|
||||
val message = RefactoringBundle.getCannotRefactorMessage("Move declaration is not supported for companion objects")
|
||||
CommonRefactoringUtil.showErrorHint(project, editor, message, MOVE_DECLARATIONS, null)
|
||||
handlerActions.showErrorHint(project, editor, message, MOVE_DECLARATIONS, null)
|
||||
return true
|
||||
}
|
||||
|
||||
if (elementsToSearch.any { !it.canMove() }) {
|
||||
val message =
|
||||
RefactoringBundle.getCannotRefactorMessage("Move declaration is only supported for top-level declarations and nested classes")
|
||||
CommonRefactoringUtil.showErrorHint(project, editor, message, MOVE_DECLARATIONS, null)
|
||||
handlerActions.showErrorHint(project, editor, message, MOVE_DECLARATIONS, null)
|
||||
return true
|
||||
}
|
||||
|
||||
if (elementsToSearch.any { it is KtEnumEntry }) {
|
||||
val message = RefactoringBundle.getCannotRefactorMessage("Move declaration is not supported for enum entries")
|
||||
CommonRefactoringUtil.showErrorHint(project, editor, message, MOVE_DECLARATIONS, null)
|
||||
handlerActions.showErrorHint(project, editor, message, MOVE_DECLARATIONS, null)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -113,11 +165,10 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
|
||||
else -> null
|
||||
}
|
||||
val initialTargetDirectory = MoveFilesOrDirectoriesUtil.resolveToDirectory(project, initialTargetElement)
|
||||
val dialog = KotlinAwareMoveFilesOrDirectoriesDialog(project, initialTargetDirectory) {
|
||||
invokeMoveFilesOrDirectoriesRefactoring(it, project, elements, initialTargetDirectory, callback)
|
||||
}
|
||||
dialog.setData(elements, initialTargetDirectory, "refactoring.moveFile")
|
||||
dialog.show()
|
||||
|
||||
handlerActions.invokeKotlinAwareMoveFilesOrDirectoriesRefactoring(
|
||||
project, initialTargetDirectory, elements, callback
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -128,12 +179,13 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
|
||||
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 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 targetFile = targetContainer as? KtFile
|
||||
val moveToPackage = targetContainer !is KtFile
|
||||
|
||||
MoveKotlinTopLevelDeclarationsDialog(
|
||||
handlerActions.invokeMoveKotlinTopLevelDeclarationsRefactoring(
|
||||
project,
|
||||
elementsToSearch,
|
||||
targetPackageName,
|
||||
@@ -142,8 +194,9 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
|
||||
moveToPackage,
|
||||
searchInComments,
|
||||
searchInText,
|
||||
deleteEmptySourceFiles,
|
||||
callback
|
||||
).show()
|
||||
)
|
||||
}
|
||||
|
||||
is KtClassOrObject -> {
|
||||
@@ -152,20 +205,20 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
|
||||
if (targetContainer !is KtClassOrObject) {
|
||||
val message =
|
||||
RefactoringBundle.getCannotRefactorMessage("Moving multiple nested classes to top-level is not supported")
|
||||
CommonRefactoringUtil.showErrorHint(project, editor, message, MOVE_DECLARATIONS, null)
|
||||
handlerActions.showErrorHint(project, editor, message, MOVE_DECLARATIONS, null)
|
||||
return true
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
MoveKotlinNestedClassesDialog(
|
||||
handlerActions.invokeMoveKotlinNestedClassesRefactoring(
|
||||
project,
|
||||
elementsToSearch.filterIsInstance<KtClassOrObject>(),
|
||||
container,
|
||||
targetContainer,
|
||||
callback
|
||||
).show()
|
||||
)
|
||||
return true
|
||||
}
|
||||
KotlinSelectNestedClassRefactoringDialog.chooseNestedClassRefactoring(
|
||||
handlerActions.invokeKotlinSelectNestedClassChooser(
|
||||
elementsToSearch.first() as KtClassOrObject,
|
||||
targetContainer
|
||||
)
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiDirectory
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.refactoring.move.MoveCallback
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||
|
||||
internal interface MoveKotlinDeclarationsHandlerActions {
|
||||
fun invokeMoveKotlinNestedClassesRefactoring(
|
||||
project: Project,
|
||||
elementsToMove: List<KtClassOrObject>,
|
||||
originalClass: KtClassOrObject,
|
||||
targetClass: KtClassOrObject,
|
||||
moveCallback: MoveCallback?
|
||||
)
|
||||
|
||||
fun invokeMoveKotlinTopLevelDeclarationsRefactoring(
|
||||
project: Project,
|
||||
elementsToMove: Set<KtNamedDeclaration>,
|
||||
targetPackageName: String,
|
||||
targetDirectory: PsiDirectory?,
|
||||
targetFile: KtFile?,
|
||||
moveToPackage: Boolean,
|
||||
searchInComments: Boolean,
|
||||
searchForTextOccurrences: Boolean,
|
||||
deleteEmptySourceFiles: Boolean,
|
||||
moveCallback: MoveCallback?
|
||||
)
|
||||
|
||||
fun invokeKotlinSelectNestedClassChooser(nestedClass: KtClassOrObject, targetContainer: PsiElement?)
|
||||
|
||||
fun invokeKotlinAwareMoveFilesOrDirectoriesRefactoring(
|
||||
project: Project, initialDirectory: PsiDirectory?, elements: Array<out PsiElement>, moveCallback: MoveCallback?
|
||||
)
|
||||
|
||||
fun showErrorHint(project: Project, editor: Editor?, message: String, title: String, helpId: String?)
|
||||
}
|
||||
+7
-1
@@ -131,7 +131,8 @@ private object ElementHashingStrategy : TObjectHashingStrategy<PsiElement> {
|
||||
|
||||
class MoveKotlinDeclarationsProcessor(
|
||||
val descriptor: MoveDeclarationsDescriptor,
|
||||
val mover: Mover = Mover.Default
|
||||
val mover: Mover = Mover.Default,
|
||||
private val throwOnConflicts: Boolean = false
|
||||
) : BaseRefactoringProcessor(descriptor.project) {
|
||||
companion object {
|
||||
private const val REFACTORING_NAME = "Move declarations"
|
||||
@@ -269,6 +270,11 @@ class MoveKotlinDeclarationsProcessor(
|
||||
return showConflicts(conflicts, refUsages.get())
|
||||
}
|
||||
|
||||
override fun showConflicts(conflicts: MultiMap<PsiElement, String>, usages: Array<out UsageInfo>?): Boolean {
|
||||
if (throwOnConflicts && !conflicts.isEmpty) throw RefactoringConflictsFoundException()
|
||||
return super.showConflicts(conflicts, usages)
|
||||
}
|
||||
|
||||
override fun performRefactoring(usages: Array<out UsageInfo>) = doPerformRefactoring(usages.toList())
|
||||
|
||||
internal fun doPerformRefactoring(usages: List<UsageInfo>) {
|
||||
|
||||
+7
-1
@@ -143,7 +143,8 @@ private object ElementHashingStrategy : TObjectHashingStrategy<PsiElement> {
|
||||
|
||||
class MoveKotlinDeclarationsProcessor(
|
||||
val descriptor: MoveDeclarationsDescriptor,
|
||||
val mover: Mover = Mover.Default) : BaseRefactoringProcessor(descriptor.project) {
|
||||
val mover: Mover = Mover.Default,
|
||||
private val throwOnConflicts: Boolean = false) : BaseRefactoringProcessor(descriptor.project) {
|
||||
companion object {
|
||||
private val REFACTORING_NAME = "Move declarations"
|
||||
val REFACTORING_ID = "move.kotlin.declarations"
|
||||
@@ -296,6 +297,11 @@ class MoveKotlinDeclarationsProcessor(
|
||||
return showConflicts(conflicts, refUsages.get())
|
||||
}
|
||||
|
||||
override fun showConflicts(conflicts: MultiMap<PsiElement, String>, usages: Array<out UsageInfo>?): Boolean {
|
||||
if (throwOnConflicts && !conflicts.isEmpty) throw RefactoringConflictsFoundException()
|
||||
return super.showConflicts(conflicts, usages)
|
||||
}
|
||||
|
||||
override fun performRefactoring(usages: Array<out UsageInfo>) = doPerformRefactoring(usages.toList())
|
||||
|
||||
internal fun doPerformRefactoring(usages: List<UsageInfo>) {
|
||||
|
||||
+7
-1
@@ -37,7 +37,8 @@ class MoveToKotlinFileProcessor @JvmOverloads constructor(
|
||||
searchInComments: Boolean,
|
||||
searchInNonJavaFiles: Boolean,
|
||||
moveCallback: MoveCallback?,
|
||||
prepareSuccessfulCallback: Runnable = EmptyRunnable.INSTANCE
|
||||
prepareSuccessfulCallback: Runnable = EmptyRunnable.INSTANCE,
|
||||
private val throwOnConflicts: Boolean = false
|
||||
) : MoveFilesOrDirectoriesProcessor(
|
||||
project,
|
||||
arrayOf(sourceFile),
|
||||
@@ -59,6 +60,11 @@ class MoveToKotlinFileProcessor @JvmOverloads constructor(
|
||||
return showConflicts(conflicts, usages)
|
||||
}
|
||||
|
||||
override fun showConflicts(conflicts: MultiMap<PsiElement, String>, usages: Array<out UsageInfo>?): Boolean {
|
||||
if (throwOnConflicts && !conflicts.isEmpty) throw RefactoringConflictsFoundException()
|
||||
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
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations
|
||||
|
||||
internal class RefactoringConflictsFoundException : Exception()
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui
|
||||
|
||||
import com.intellij.openapi.options.ConfigurationException
|
||||
|
||||
internal interface Model<out T> {
|
||||
@Throws(ConfigurationException::class)
|
||||
fun computeModelResult(throwOnConflicts: Boolean = false): T
|
||||
|
||||
@Throws(ConfigurationException::class)
|
||||
fun computeModelResult(): T
|
||||
}
|
||||
+59
-54
@@ -5,49 +5,51 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui
|
||||
|
||||
import com.intellij.ide.util.DirectoryUtil
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.IdeActions
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||
import com.intellij.openapi.fileChooser.FileChooserFactory
|
||||
import com.intellij.openapi.help.HelpManager
|
||||
import com.intellij.openapi.keymap.KeymapUtil
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.options.ConfigurationException
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.DialogWrapper
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.ui.TextComponentAccessor
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.refactoring.RefactoringBundle
|
||||
import com.intellij.refactoring.copy.CopyFilesOrDirectoriesDialog
|
||||
import com.intellij.refactoring.copy.CopyFilesOrDirectoriesHandler
|
||||
import com.intellij.refactoring.move.MoveCallback
|
||||
import com.intellij.refactoring.move.MoveHandler
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||
import com.intellij.ui.NonFocusableCheckBox
|
||||
import com.intellij.ui.RecentsManager
|
||||
import com.intellij.ui.TextFieldWithHistoryWithBrowseButton
|
||||
import com.intellij.ui.components.JBLabelDecorator
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import com.intellij.util.ui.FormBuilder
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import org.jetbrains.kotlin.idea.core.packageMatchesDirectoryOrImplicit
|
||||
import org.jetbrains.kotlin.idea.core.util.onTextChange
|
||||
import org.jetbrains.kotlin.idea.refactoring.isInKotlinAwareSourceRoot
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.KotlinAwareMoveFilesOrDirectoriesProcessor
|
||||
import org.jetbrains.kotlin.idea.util.application.executeCommand
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import java.io.File
|
||||
import javax.swing.JComponent
|
||||
|
||||
class KotlinAwareMoveFilesOrDirectoriesDialog(
|
||||
private val project: Project,
|
||||
private val initialDirectory: PsiDirectory?,
|
||||
private val callback: (KotlinAwareMoveFilesOrDirectoriesDialog?) -> Unit
|
||||
private val psiElements: Array<out PsiElement>,
|
||||
private val callback: MoveCallback?
|
||||
) : DialogWrapper(project, true) {
|
||||
|
||||
companion object {
|
||||
private const val RECENT_KEYS = "MoveFile.RECENT_KEYS"
|
||||
private const val MOVE_FILES_OPEN_IN_EDITOR = "MoveFile.OpenInEditor"
|
||||
private const val HELP_ID = "refactoring.moveFile"
|
||||
}
|
||||
|
||||
private val nameLabel = JBLabelDecorator.createJBLabelDecorator().setBold(true)
|
||||
@@ -56,26 +58,19 @@ class KotlinAwareMoveFilesOrDirectoriesDialog(
|
||||
private val openInEditorCb = NonFocusableCheckBox("Open moved files in editor")
|
||||
private val updatePackageDirectiveCb = NonFocusableCheckBox()
|
||||
|
||||
private var helpID: String? = null
|
||||
var targetDirectory: PsiDirectory? = null
|
||||
private set
|
||||
override fun getHelpId() = HELP_ID
|
||||
|
||||
init {
|
||||
title = RefactoringBundle.message("move.title")
|
||||
init()
|
||||
initializeData()
|
||||
}
|
||||
|
||||
val updatePackageDirective: Boolean
|
||||
get() = updatePackageDirectiveCb.isSelected
|
||||
|
||||
val searchReferences: Boolean
|
||||
get() = searchReferencesCb.isSelected
|
||||
|
||||
override fun createActions() = arrayOf(okAction, cancelAction, helpAction)
|
||||
|
||||
override fun getPreferredFocusedComponent() = targetDirectoryField.childComponent
|
||||
|
||||
override fun createCenterPanel() = null
|
||||
override fun createCenterPanel(): JComponent? = null
|
||||
|
||||
override fun createNorthPanel(): JComponent {
|
||||
RecentsManager.getInstance(project).getRecentEntries(RECENT_KEYS)?.let { targetDirectoryField.childComponent.history = it }
|
||||
@@ -107,7 +102,7 @@ class KotlinAwareMoveFilesOrDirectoriesDialog(
|
||||
.panel
|
||||
}
|
||||
|
||||
fun setData(psiElements: Array<out PsiElement>, initialTargetDirectory: PsiDirectory?, helpID: String) {
|
||||
private fun initializeData() {
|
||||
val psiElement = psiElements.singleOrNull()
|
||||
if (psiElement != null) {
|
||||
val shortenedPath = CopyFilesOrDirectoriesDialog.shortenPath((psiElement as PsiFileSystemItem).virtualFile)
|
||||
@@ -125,10 +120,9 @@ class KotlinAwareMoveFilesOrDirectoriesDialog(
|
||||
}
|
||||
}
|
||||
|
||||
targetDirectoryField.childComponent.text = initialTargetDirectory?.virtualFile?.presentableUrl ?: ""
|
||||
targetDirectoryField.childComponent.text = initialDirectory?.virtualFile?.presentableUrl ?: ""
|
||||
|
||||
validateOKButton()
|
||||
this.helpID = helpID
|
||||
|
||||
with(updatePackageDirectiveCb) {
|
||||
val jetFiles = psiElements.filterIsInstance<KtFile>().filter(KtFile::isInKotlinAwareSourceRoot)
|
||||
@@ -144,8 +138,6 @@ class KotlinAwareMoveFilesOrDirectoriesDialog(
|
||||
}
|
||||
}
|
||||
|
||||
override fun doHelpAction() = HelpManager.getInstance().invokeHelp(helpID)
|
||||
|
||||
private fun isOpenInEditor(): Boolean {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) return false
|
||||
return PropertiesComponent.getInstance().getBoolean(MOVE_FILES_OPEN_IN_EDITOR, false)
|
||||
@@ -155,41 +147,54 @@ class KotlinAwareMoveFilesOrDirectoriesDialog(
|
||||
isOKActionEnabled = targetDirectoryField.childComponent.text.isNotEmpty()
|
||||
}
|
||||
|
||||
override fun doOKAction() {
|
||||
PropertiesComponent.getInstance().setValue(MOVE_FILES_OPEN_IN_EDITOR, openInEditorCb.isSelected, false)
|
||||
RecentsManager.getInstance(project).registerRecentEntry(RECENT_KEYS, targetDirectoryField.childComponent.text)
|
||||
private val selectedDirectoryName: String
|
||||
get() = targetDirectoryField.childComponent.text.let {
|
||||
when {
|
||||
it.startsWith(".") -> (initialDirectory?.virtualFile?.path ?: "") + "/" + it
|
||||
else -> it
|
||||
}
|
||||
}
|
||||
|
||||
if (DumbService.isDumb(project)) {
|
||||
Messages.showMessageDialog(project, "Move refactoring is not available while indexing is in progress", "Indexing", null)
|
||||
private fun getModel(): Model<KotlinAwareMoveFilesOrDirectoriesProcessor> {
|
||||
|
||||
val directory = LocalFileSystem.getInstance().findFileByPath(selectedDirectoryName)?.let {
|
||||
PsiManager.getInstance(project).findDirectory(it)
|
||||
}
|
||||
|
||||
val elementsToMove: List<PsiElement> = directory?.let { existentDirectory ->
|
||||
val choice = if (psiElements.size > 1 || psiElements[0] is PsiDirectory) intArrayOf(-1) else null
|
||||
psiElements.filterNot {
|
||||
it is PsiFile && CopyFilesOrDirectoriesHandler.checkFileExist(existentDirectory, choice, it, it.name, "Move")
|
||||
}
|
||||
} ?: psiElements.toList()
|
||||
|
||||
return KotlinAwareMoveFilesOrDirectoriesModel(
|
||||
project,
|
||||
elementsToMove,
|
||||
selectedDirectoryName,
|
||||
updatePackageDirectiveCb.isSelected,
|
||||
searchReferencesCb.isSelected,
|
||||
callback
|
||||
)
|
||||
}
|
||||
|
||||
override fun doOKAction() {
|
||||
|
||||
val processor: KotlinAwareMoveFilesOrDirectoriesProcessor
|
||||
try {
|
||||
processor = getModel().computeModelResult()
|
||||
} catch (e: ConfigurationException) {
|
||||
setErrorText(e.message)
|
||||
return
|
||||
}
|
||||
|
||||
project.executeCommand(RefactoringBundle.message("move.title"), null) {
|
||||
runWriteAction {
|
||||
val directoryName = targetDirectoryField.childComponent.text.replace(File.separatorChar, '/').let {
|
||||
when {
|
||||
it.startsWith(".") -> (initialDirectory?.virtualFile?.path ?: "") + "/" + it
|
||||
else -> it
|
||||
}
|
||||
}
|
||||
try {
|
||||
targetDirectory = DirectoryUtil.mkdirs(PsiManager.getInstance(project), directoryName)
|
||||
} catch (e: IncorrectOperationException) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
if (targetDirectory == null) {
|
||||
CommonRefactoringUtil.showErrorMessage(
|
||||
title,
|
||||
RefactoringBundle.message("cannot.create.directory"),
|
||||
helpID,
|
||||
project
|
||||
)
|
||||
return@executeCommand
|
||||
}
|
||||
|
||||
callback(this@KotlinAwareMoveFilesOrDirectoriesDialog)
|
||||
project.executeCommand(MoveHandler.REFACTORING_NAME) {
|
||||
processor.run()
|
||||
}
|
||||
|
||||
PropertiesComponent.getInstance().setValue(MOVE_FILES_OPEN_IN_EDITOR, openInEditorCb.isSelected, false)
|
||||
RecentsManager.getInstance(project).registerRecentEntry(RECENT_KEYS, targetDirectoryField.childComponent.text)
|
||||
|
||||
close(OK_EXIT_CODE, true)
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui
|
||||
|
||||
import com.intellij.openapi.options.ConfigurationException
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiDirectory
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.refactoring.move.MoveCallback
|
||||
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import org.jetbrains.kotlin.idea.refactoring.isInKotlinAwareSourceRoot
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.getOrCreateDirectory
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.KotlinAwareMoveFilesOrDirectoriesProcessor
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.updatePackageDirective
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
class KotlinAwareMoveFilesOrDirectoriesModel(
|
||||
val project: Project,
|
||||
val elementsToMove: List<PsiElement>,
|
||||
val targetDirectoryName: String,
|
||||
val updatePackageDirective: Boolean,
|
||||
val searchReferences: Boolean,
|
||||
val moveCallback: MoveCallback?
|
||||
) : Model<KotlinAwareMoveFilesOrDirectoriesProcessor> {
|
||||
|
||||
private fun checkedGetElementsToMove(selectedDirectory: PsiDirectory): List<PsiElement> {
|
||||
|
||||
val preparedElementsToMove = elementsToMove
|
||||
.filterNot { it is PsiFile && it.containingDirectory == selectedDirectory }
|
||||
.sortedWith( // process Kotlin files first so that light classes are updated before changing references in Java files
|
||||
java.util.Comparator { o1, o2 ->
|
||||
when {
|
||||
o1 is KtElement && o2 !is KtElement -> -1
|
||||
o1 !is KtElement && o2 is KtElement -> 1
|
||||
else -> 0
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
preparedElementsToMove.forEach {
|
||||
MoveFilesOrDirectoriesUtil.checkMove(it, selectedDirectory)
|
||||
if (it is KtFile && it.isInKotlinAwareSourceRoot()) {
|
||||
it.updatePackageDirective = updatePackageDirective
|
||||
}
|
||||
}
|
||||
} catch (e: IncorrectOperationException) {
|
||||
throw ConfigurationException(e.message)
|
||||
}
|
||||
|
||||
return preparedElementsToMove
|
||||
}
|
||||
|
||||
private fun checkedGetTargetDirectory(): PsiDirectory {
|
||||
try {
|
||||
return getOrCreateDirectory(targetDirectoryName, project)
|
||||
} catch (e: IncorrectOperationException) {
|
||||
throw ConfigurationException("Cannot create target directory $targetDirectoryName")
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(ConfigurationException::class)
|
||||
override fun computeModelResult() = computeModelResult(throwOnConflicts = false)
|
||||
|
||||
private fun checkModel() {
|
||||
|
||||
elementsToMove.firstOrNull { it !is PsiFile && it !is PsiDirectory }?.let {
|
||||
throw ConfigurationException("Unexpected element type: $it")
|
||||
}
|
||||
|
||||
if (elementsToMove.isEmpty()) {
|
||||
throw ConfigurationException("There is no given files to move")
|
||||
}
|
||||
|
||||
try {
|
||||
Paths.get(targetDirectoryName)
|
||||
} catch (e: InvalidPathException) {
|
||||
throw ConfigurationException("Invalid target path $targetDirectoryName")
|
||||
}
|
||||
|
||||
if (DumbService.isDumb(project)) {
|
||||
throw ConfigurationException("Move refactoring is not available while indexing is in progress")
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(ConfigurationException::class)
|
||||
override fun computeModelResult(throwOnConflicts: Boolean): KotlinAwareMoveFilesOrDirectoriesProcessor {
|
||||
|
||||
checkModel()
|
||||
|
||||
val selectedDir = checkedGetTargetDirectory()
|
||||
|
||||
return KotlinAwareMoveFilesOrDirectoriesProcessor(
|
||||
project,
|
||||
checkedGetElementsToMove(selectedDir),
|
||||
selectedDir,
|
||||
searchReferences = searchReferences,
|
||||
searchInComments = false,
|
||||
searchInNonJavaFiles = false,
|
||||
moveCallback = moveCallback
|
||||
)
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -104,7 +104,12 @@ internal class KotlinSelectNestedClassRefactoringDialog private constructor(
|
||||
}
|
||||
nestedClass is KtEnumEntry -> return
|
||||
else -> {
|
||||
val selectionDialog = KotlinSelectNestedClassRefactoringDialog(project, nestedClass, targetContainer)
|
||||
val selectionDialog =
|
||||
KotlinSelectNestedClassRefactoringDialog(
|
||||
project,
|
||||
nestedClass,
|
||||
targetContainer
|
||||
)
|
||||
selectionDialog.show()
|
||||
if (selectionDialog.exitCode != OK_EXIT_CODE) return
|
||||
selectionDialog.getNextDialog() ?: return
|
||||
|
||||
+22
-35
@@ -15,7 +15,6 @@ import com.intellij.psi.JavaPsiFacade;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.refactoring.RefactoringBundle;
|
||||
import com.intellij.refactoring.classMembers.AbstractMemberInfoModel;
|
||||
import com.intellij.refactoring.classMembers.MemberInfoChange;
|
||||
@@ -38,7 +37,6 @@ import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionTab
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*;
|
||||
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinTypeReferenceEditorComboWithBrowseButton;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import java.awt.*;
|
||||
@@ -82,6 +80,8 @@ public class MoveKotlinNestedClassesDialog extends RefactoringDialog {
|
||||
initMemberInfo(elementsToMove);
|
||||
|
||||
validateButtons();
|
||||
|
||||
myHelpAction.setEnabled(false);
|
||||
}
|
||||
|
||||
private void initClassChooser(@NotNull KtClassOrObject initialTargetClass) {
|
||||
@@ -217,40 +217,29 @@ public class MoveKotlinNestedClassesDialog extends RefactoringDialog {
|
||||
return "#" + getClass().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void canRun() throws ConfigurationException {
|
||||
if (targetClass == null) throw new ConfigurationException("No destination class specified");
|
||||
|
||||
if (!(targetClass instanceof KtClassOrObject)) throw new ConfigurationException("Destination class must be a Kotlin class");
|
||||
|
||||
if (originalClass == targetClass) {
|
||||
throw new ConfigurationException(RefactoringBundle.message("source.and.destination.classes.should.be.different"));
|
||||
}
|
||||
|
||||
for (KtClassOrObject classOrObject : getSelectedElementsToMove()) {
|
||||
if (PsiTreeUtil.isAncestor(classOrObject, targetClass, false)) {
|
||||
throw new ConfigurationException("Cannot move nested class " + classOrObject.getName() + " to itself");
|
||||
}
|
||||
}
|
||||
private Model<MoveKotlinDeclarationsProcessor> getModel() {
|
||||
return new MoveKotlinNestedClassesModel(
|
||||
myProject,
|
||||
openInEditorCheckBox.isSelected(),
|
||||
getSelectedElementsToMove(),
|
||||
originalClass,
|
||||
targetClass,
|
||||
moveCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doAction() {
|
||||
List<KtClassOrObject> elementsToMove = getSelectedElementsToMove();
|
||||
KotlinMoveTarget target = new KotlinMoveTargetForExistingElement((KtClassOrObject) targetClass);
|
||||
MoveDeclarationsDelegate.NestedClass delegate = new MoveDeclarationsDelegate.NestedClass();
|
||||
MoveDeclarationsDescriptor descriptor = new MoveDeclarationsDescriptor(
|
||||
myProject,
|
||||
MoveKotlinDeclarationsProcessorKt.MoveSource(elementsToMove),
|
||||
target,
|
||||
delegate,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
moveCallback,
|
||||
openInEditorCheckBox.isSelected()
|
||||
);
|
||||
invokeRefactoring(new MoveKotlinDeclarationsProcessor(descriptor, Mover.Default.INSTANCE));
|
||||
|
||||
MoveKotlinDeclarationsProcessor processor;
|
||||
try {
|
||||
processor = getModel().computeModelResult();
|
||||
}
|
||||
catch (ConfigurationException e) {
|
||||
setErrorText(e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
invokeRefactoring(processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -258,7 +247,5 @@ public class MoveKotlinNestedClassesDialog extends RefactoringDialog {
|
||||
return targetClassChooser.getChildComponent();
|
||||
}
|
||||
|
||||
private static class MemberInfoModelImpl extends AbstractMemberInfoModel<KtNamedDeclaration, KotlinMemberInfo> {
|
||||
|
||||
}
|
||||
private static class MemberInfoModelImpl extends AbstractMemberInfoModel<KtNamedDeclaration, KotlinMemberInfo> { }
|
||||
}
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui
|
||||
|
||||
import com.intellij.openapi.options.ConfigurationException
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.refactoring.RefactoringBundle
|
||||
import com.intellij.refactoring.move.MoveCallback
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
|
||||
internal class MoveKotlinNestedClassesModel(
|
||||
val project: Project,
|
||||
val openInEditorCheckBox: Boolean,
|
||||
val selectedElementsToMove: List<KtClassOrObject>,
|
||||
val originalClass: KtClassOrObject,
|
||||
val targetClass: PsiElement?,
|
||||
val moveCallback: MoveCallback?
|
||||
) : Model<MoveKotlinDeclarationsProcessor> {
|
||||
|
||||
private fun getCheckedTargetClass(): KtElement {
|
||||
val targetClass = this.targetClass ?: throw ConfigurationException("No destination class specified")
|
||||
|
||||
if (targetClass !is KtClassOrObject){
|
||||
throw ConfigurationException("Destination class must be a Kotlin class")
|
||||
}
|
||||
|
||||
if (originalClass === targetClass) {
|
||||
throw ConfigurationException(RefactoringBundle.message("source.and.destination.classes.should.be.different"))
|
||||
}
|
||||
|
||||
for (classOrObject in selectedElementsToMove) {
|
||||
if (PsiTreeUtil.isAncestor(classOrObject, targetClass, false)) {
|
||||
throw ConfigurationException("Cannot move nested class ${classOrObject.name} to itself")
|
||||
}
|
||||
}
|
||||
|
||||
return targetClass
|
||||
}
|
||||
|
||||
@Throws(ConfigurationException::class)
|
||||
override fun computeModelResult() = computeModelResult(throwOnConflicts = false)
|
||||
|
||||
@Throws(ConfigurationException::class)
|
||||
override fun computeModelResult(throwOnConflicts: Boolean): MoveKotlinDeclarationsProcessor {
|
||||
val elementsToMove = selectedElementsToMove
|
||||
val target = KotlinMoveTargetForExistingElement(getCheckedTargetClass())
|
||||
val delegate = MoveDeclarationsDelegate.NestedClass()
|
||||
val descriptor = MoveDeclarationsDescriptor(
|
||||
project,
|
||||
MoveSource(elementsToMove),
|
||||
target,
|
||||
delegate,
|
||||
searchInCommentsAndStrings = false,
|
||||
searchInNonCode = false,
|
||||
deleteSourceFiles = false,
|
||||
moveCallback = moveCallback,
|
||||
openInEditor = openInEditorCheckBox
|
||||
)
|
||||
|
||||
return MoveKotlinDeclarationsProcessor(descriptor, Mover.Default, throwOnConflicts)
|
||||
}
|
||||
}
|
||||
+24
-194
@@ -5,53 +5,35 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.options.ConfigurationException;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.NullableComputable;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.PsiDirectory;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.refactoring.HelpID;
|
||||
import com.intellij.refactoring.PackageWrapper;
|
||||
import com.intellij.refactoring.RefactoringBundle;
|
||||
import com.intellij.refactoring.move.MoveDialogBase;
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesUtil;
|
||||
import com.intellij.refactoring.move.moveInner.MoveInnerImpl;
|
||||
import com.intellij.refactoring.ui.NameSuggestionsField;
|
||||
import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo;
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil;
|
||||
import com.intellij.refactoring.util.RefactoringMessageUtil;
|
||||
import com.intellij.refactoring.util.RefactoringUtil;
|
||||
import com.intellij.ui.EditorTextField;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor;
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType;
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
|
||||
import org.jetbrains.kotlin.idea.core.CollectingNameValidator;
|
||||
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester;
|
||||
import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator;
|
||||
import org.jetbrains.kotlin.idea.core.PackageUtilsKt;
|
||||
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringSettings;
|
||||
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtilKt;
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.MoveUtilsKt;
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*;
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveKotlinDeclarationsProcessor;
|
||||
import org.jetbrains.kotlin.name.FqName;
|
||||
import org.jetbrains.kotlin.name.Name;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt;
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils;
|
||||
import org.jetbrains.kotlin.psi.KtClass;
|
||||
import org.jetbrains.kotlin.psi.KtClassBody;
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject;
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;
|
||||
import org.jetbrains.kotlin.types.KotlinType;
|
||||
import static org.jetbrains.kotlin.idea.roots.ProjectRootUtilsKt.getSuitableDestinationSourceRoots;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
@@ -96,16 +78,6 @@ public class MoveKotlinNestedClassesToUpperLevelDialog extends MoveDialogBase {
|
||||
openInEditorPanel.add(initOpenInEditorCb(), BorderLayout.EAST);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static FqName getTargetPackageFqName(PsiElement targetContainer) {
|
||||
if (targetContainer instanceof PsiDirectory) {
|
||||
PsiPackage targetPackage = PackageUtilsKt.getPackage((PsiDirectory) targetContainer);
|
||||
return targetPackage != null ? new FqName(targetPackage.getQualifiedName()) : null;
|
||||
}
|
||||
if (targetContainer instanceof KtFile) return ((KtFile) targetContainer).getPackageFqName();
|
||||
return null;
|
||||
}
|
||||
|
||||
private void createUIComponents() {
|
||||
parameterField = new NameSuggestionsField(project);
|
||||
packageNameField = new PackageNameReferenceEditorCombo("", project, RECENTS_KEY,
|
||||
@@ -127,14 +99,6 @@ public class MoveKotlinNestedClassesToUpperLevelDialog extends MoveDialogBase {
|
||||
return "Open moved member in editor";
|
||||
}
|
||||
|
||||
public boolean isSearchInComments() {
|
||||
return searchInCommentsCheckBox.isSelected();
|
||||
}
|
||||
|
||||
public boolean isSearchInNonJavaFiles() {
|
||||
return searchForTextOccurrencesCheckBox.isSelected();
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return classNameField.getText().trim();
|
||||
}
|
||||
@@ -150,7 +114,7 @@ public class MoveKotlinNestedClassesToUpperLevelDialog extends MoveDialogBase {
|
||||
|
||||
@Nullable
|
||||
private FqName getTargetPackageFqName() {
|
||||
return getTargetPackageFqName(targetContainer);
|
||||
return MoveUtilsKt.getTargetPackageFqName(targetContainer);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -236,127 +200,30 @@ public class MoveKotlinNestedClassesToUpperLevelDialog extends MoveDialogBase {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiElement getTargetContainer() {
|
||||
if (targetContainer instanceof PsiDirectory) {
|
||||
PsiDirectory psiDirectory = (PsiDirectory) targetContainer;
|
||||
FqName oldPackageFqName = getTargetPackageFqName();
|
||||
String targetName = packageNameField.getText();
|
||||
if (!Comparing.equal(oldPackageFqName != null ? oldPackageFqName.asString() : null, targetName)) {
|
||||
ProjectRootManager projectRootManager = ProjectRootManager.getInstance(project);
|
||||
|
||||
List<VirtualFile> contentSourceRoots = getSuitableDestinationSourceRoots(project);
|
||||
PackageWrapper newPackage = new PackageWrapper(PsiManager.getInstance(project), targetName);
|
||||
VirtualFile targetSourceRoot;
|
||||
|
||||
if (contentSourceRoots.size() > 1) {
|
||||
PsiDirectory initialDir = null;
|
||||
PsiPackage oldPackage = oldPackageFqName != null
|
||||
? JavaPsiFacade.getInstance(project).findPackage(oldPackageFqName.asString())
|
||||
: null;
|
||||
if (oldPackage != null) {
|
||||
PsiDirectory[] directories = oldPackage.getDirectories();
|
||||
VirtualFile root = projectRootManager.getFileIndex().getContentRootForFile(psiDirectory.getVirtualFile());
|
||||
for (PsiDirectory dir : directories) {
|
||||
if (Comparing.equal(projectRootManager.getFileIndex().getContentRootForFile(dir.getVirtualFile()), root)) {
|
||||
initialDir = dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
VirtualFile sourceRoot = MoveClassesOrPackagesUtil.chooseSourceRoot(newPackage, contentSourceRoots, initialDir);
|
||||
if (sourceRoot == null) return null;
|
||||
targetSourceRoot = sourceRoot;
|
||||
}
|
||||
else {
|
||||
targetSourceRoot = contentSourceRoots.get(0);
|
||||
}
|
||||
PsiDirectory dir = RefactoringUtil.findPackageDirectoryInSourceRoot(newPackage, targetSourceRoot);
|
||||
if (dir == null) {
|
||||
dir = ApplicationManager.getApplication().runWriteAction((NullableComputable<PsiDirectory>) () -> {
|
||||
try {
|
||||
return RefactoringUtil.createPackageDirectoryInSourceRoot(newPackage, targetSourceRoot);
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
return targetContainer;
|
||||
}
|
||||
|
||||
if (targetContainer instanceof KtFile || targetContainer instanceof KtClassOrObject) return targetContainer;
|
||||
|
||||
return null;
|
||||
private Model<MoveKotlinDeclarationsProcessor> getModel() {
|
||||
return new MoveKotlinNestedClassesToUpperLevelModel(
|
||||
project,
|
||||
innerClass,
|
||||
targetContainer,
|
||||
getParameterName(),
|
||||
getClassName(),
|
||||
passOuterClassCheckBox.isSelected(),
|
||||
searchInCommentsCheckBox.isSelected(),
|
||||
searchForTextOccurrencesCheckBox.isSelected(),
|
||||
packageNameField.getText(),
|
||||
isOpenInEditor());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiElement getTargetContainerWithValidation() throws ConfigurationException {
|
||||
String className = getClassName();
|
||||
String parameterName = getParameterName();
|
||||
|
||||
if (className != null && className.isEmpty()) {
|
||||
throw new ConfigurationException(RefactoringBundle.message("no.class.name.specified"));
|
||||
}
|
||||
if (!KtPsiUtilKt.isIdentifier(className)) {
|
||||
throw new ConfigurationException(RefactoringMessageUtil.getIncorrectIdentifierMessage(className));
|
||||
}
|
||||
|
||||
if (passOuterClassCheckBox.isSelected()) {
|
||||
if (parameterName != null && parameterName.isEmpty()) {
|
||||
throw new ConfigurationException(RefactoringBundle.message("no.parameter.name.specified"));
|
||||
}
|
||||
if (!KtPsiUtilKt.isIdentifier(parameterName)) {
|
||||
throw new ConfigurationException(RefactoringMessageUtil.getIncorrectIdentifierMessage(parameterName));
|
||||
}
|
||||
}
|
||||
|
||||
PsiElement targetContainer = getTargetContainer();
|
||||
|
||||
if (targetContainer instanceof KtClassOrObject) {
|
||||
KtClassOrObject targetClass = (KtClassOrObject) targetContainer;
|
||||
for (KtDeclaration member : targetClass.getDeclarations()) {
|
||||
if (member instanceof KtClassOrObject && className != null && className.equals(member.getName())) {
|
||||
throw new ConfigurationException(RefactoringBundle.message("inner.class.exists", className, targetClass.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetContainer instanceof PsiDirectory || targetContainer instanceof KtFile) {
|
||||
FqName targetPackageFqName = getTargetPackageFqName();
|
||||
if (targetPackageFqName == null) throw new ConfigurationException("No package corresponds to this directory");
|
||||
|
||||
//noinspection ConstantConditions
|
||||
ClassifierDescriptor existingClass = DescriptorUtils
|
||||
.getContainingModule(innerClassDescriptor)
|
||||
.getPackage(targetPackageFqName)
|
||||
.getMemberScope()
|
||||
.getContributedClassifier(Name.identifier(className), NoLookupLocation.FROM_IDE);
|
||||
if (existingClass != null) {
|
||||
throw new ConfigurationException("Class " + className + " already exists in package " + targetPackageFqName);
|
||||
}
|
||||
|
||||
PsiDirectory targetDir = targetContainer instanceof PsiDirectory
|
||||
? (PsiDirectory) targetContainer
|
||||
: targetContainer.getContainingFile().getContainingDirectory();
|
||||
String message = RefactoringMessageUtil.checkCanCreateFile(targetDir, className + ".kt");
|
||||
if (message != null) throw new ConfigurationException(message);
|
||||
}
|
||||
|
||||
return targetContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doAction() {
|
||||
PsiElement target;
|
||||
|
||||
MoveKotlinDeclarationsProcessor processor;
|
||||
try {
|
||||
target = getTargetContainerWithValidation();
|
||||
if (target == null) return;
|
||||
processor = getModel().computeModelResult();
|
||||
}
|
||||
catch (ConfigurationException e) {
|
||||
CommonRefactoringUtil.showErrorMessage(MoveInnerImpl.REFACTORING_NAME, e.getMessage(), HelpID.MOVE_INNER_UPPER, project);
|
||||
setErrorText(e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -364,45 +231,8 @@ public class MoveKotlinNestedClassesToUpperLevelDialog extends MoveDialogBase {
|
||||
settings.MOVE_TO_UPPER_LEVEL_SEARCH_FOR_TEXT = searchForTextOccurrencesCheckBox.isSelected();
|
||||
settings.MOVE_TO_UPPER_LEVEL_SEARCH_IN_COMMENTS = searchInCommentsCheckBox.isSelected();
|
||||
|
||||
String newClassName = getClassName();
|
||||
|
||||
KotlinMoveTarget moveTarget;
|
||||
if (target instanceof PsiDirectory) {
|
||||
PsiDirectory targetDir = (PsiDirectory) target;
|
||||
|
||||
FqName targetPackageFqName = getTargetPackageFqName(target);
|
||||
if (targetPackageFqName == null) return;
|
||||
|
||||
String targetFileName = KotlinNameSuggester.INSTANCE.suggestNameByName(
|
||||
newClassName,
|
||||
s -> targetDir.findFile(s + "." + KotlinFileType.EXTENSION) == null
|
||||
) + "." + KotlinFileType.EXTENSION;
|
||||
moveTarget = new KotlinMoveTargetForDeferredFile(
|
||||
targetPackageFqName,
|
||||
targetDir,
|
||||
null,
|
||||
originalFile -> KotlinRefactoringUtilKt.createKotlinFile(targetFileName, targetDir, targetPackageFqName.asString())
|
||||
);
|
||||
}
|
||||
else {
|
||||
moveTarget = new KotlinMoveTargetForExistingElement((KtElement) target);
|
||||
}
|
||||
|
||||
String outerInstanceParameterName = passOuterClassCheckBox.isSelected() ? getParameterName() : null;
|
||||
MoveDeclarationsDelegate delegate = new MoveDeclarationsDelegate.NestedClass(newClassName, outerInstanceParameterName);
|
||||
MoveDeclarationsDescriptor moveDescriptor = new MoveDeclarationsDescriptor(
|
||||
project,
|
||||
MoveKotlinDeclarationsProcessorKt.MoveSource(innerClass),
|
||||
moveTarget,
|
||||
delegate,
|
||||
isSearchInComments(),
|
||||
isSearchInNonJavaFiles(),
|
||||
false,
|
||||
null,
|
||||
isOpenInEditor()
|
||||
);
|
||||
saveOpenInEditorOption();
|
||||
|
||||
invokeRefactoring(new MoveKotlinDeclarationsProcessor(moveDescriptor, Mover.Default.INSTANCE));
|
||||
invokeRefactoring(processor);
|
||||
}
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui
|
||||
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.options.ConfigurationException
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ProjectRootManager
|
||||
import com.intellij.openapi.util.Comparing
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiDirectory
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.refactoring.PackageWrapper
|
||||
import com.intellij.refactoring.RefactoringBundle
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesUtil
|
||||
import com.intellij.refactoring.util.RefactoringMessageUtil
|
||||
import com.intellij.refactoring.util.RefactoringUtil
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
||||
import org.jetbrains.kotlin.idea.refactoring.createKotlinFile
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.getTargetPackageFqName
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*
|
||||
import org.jetbrains.kotlin.idea.roots.getSuitableDestinationSourceRoots
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isIdentifier
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
|
||||
internal class MoveKotlinNestedClassesToUpperLevelModel(
|
||||
val project: Project,
|
||||
val innerClass: KtClassOrObject,
|
||||
val target: PsiElement,
|
||||
val parameter: String?,
|
||||
val className: String,
|
||||
val passOuterClass: Boolean,
|
||||
val searchInComments: Boolean,
|
||||
val isSearchInNonJavaFiles: Boolean,
|
||||
val packageName: String,
|
||||
val isOpenInEditor: Boolean
|
||||
) : Model<MoveKotlinDeclarationsProcessor> {
|
||||
|
||||
private val innerClassDescriptor = innerClass.unsafeResolveToDescriptor(BodyResolveMode.FULL) as ClassDescriptor
|
||||
|
||||
private fun getTargetContainer(): PsiElement? {
|
||||
if (target is PsiDirectory) {
|
||||
val oldPackageFqName = getTargetPackageFqName(target)
|
||||
val targetName = packageName
|
||||
if (!Comparing.equal(oldPackageFqName?.asString(), targetName)) {
|
||||
val projectRootManager = ProjectRootManager.getInstance(project)
|
||||
|
||||
val contentSourceRoots = getSuitableDestinationSourceRoots(project)
|
||||
val newPackage = PackageWrapper(PsiManager.getInstance(project), targetName)
|
||||
|
||||
val targetSourceRoot: VirtualFile
|
||||
if (contentSourceRoots.size > 1) {
|
||||
val oldPackage = oldPackageFqName?.let {
|
||||
JavaPsiFacade.getInstance(project).findPackage(it.asString())
|
||||
}
|
||||
|
||||
var initialDir: PsiDirectory? = null
|
||||
if (oldPackage != null) {
|
||||
val root = projectRootManager.fileIndex.getContentRootForFile(target.virtualFile)
|
||||
initialDir = oldPackage.directories.firstOrNull {
|
||||
Comparing.equal(projectRootManager.fileIndex.getContentRootForFile(it.virtualFile), root)
|
||||
}
|
||||
}
|
||||
|
||||
targetSourceRoot = MoveClassesOrPackagesUtil.chooseSourceRoot(newPackage, contentSourceRoots, initialDir) ?: return null
|
||||
} else {
|
||||
targetSourceRoot = contentSourceRoots[0]
|
||||
}
|
||||
|
||||
var directory = RefactoringUtil.findPackageDirectoryInSourceRoot(newPackage, targetSourceRoot);
|
||||
if (directory === null) {
|
||||
runWriteAction {
|
||||
try {
|
||||
directory = RefactoringUtil.createPackageDirectoryInSourceRoot(newPackage, targetSourceRoot)
|
||||
} catch (e: IncorrectOperationException) {
|
||||
directory = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return directory;
|
||||
}
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
return if (target is KtFile || target is KtClassOrObject) target else null
|
||||
|
||||
}
|
||||
|
||||
@Throws(ConfigurationException::class)
|
||||
private fun getTargetContainerWithValidation(): PsiElement {
|
||||
|
||||
if (className.isEmpty()) {
|
||||
throw ConfigurationException(RefactoringBundle.message("no.class.name.specified"))
|
||||
}
|
||||
if (!className.isIdentifier()) {
|
||||
throw ConfigurationException(RefactoringMessageUtil.getIncorrectIdentifierMessage(className))
|
||||
}
|
||||
|
||||
if (passOuterClass) {
|
||||
if (parameter.isNullOrEmpty()) {
|
||||
throw ConfigurationException(RefactoringBundle.message("no.parameter.name.specified"))
|
||||
}
|
||||
if (!parameter.isIdentifier()) {
|
||||
throw ConfigurationException(RefactoringMessageUtil.getIncorrectIdentifierMessage(parameter))
|
||||
}
|
||||
}
|
||||
|
||||
val targetContainer = getTargetContainer()
|
||||
|
||||
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 (targetContainer is PsiDirectory || targetContainer is KtFile) {
|
||||
val targetPackageFqName = getTargetPackageFqName(target)
|
||||
?: throw ConfigurationException("No package corresponds to this directory")
|
||||
|
||||
val existingClass = DescriptorUtils
|
||||
.getContainingModule(innerClassDescriptor)
|
||||
.getPackage(targetPackageFqName)
|
||||
.memberScope
|
||||
.getContributedClassifier(Name.identifier(className), NoLookupLocation.FROM_IDE)
|
||||
if (existingClass != null) {
|
||||
throw ConfigurationException("Class $className already exists in package $targetPackageFqName")
|
||||
}
|
||||
|
||||
val targetDir = targetContainer as? PsiDirectory ?: targetContainer.containingFile.containingDirectory
|
||||
val message = RefactoringMessageUtil.checkCanCreateFile(targetDir, "$className.kt")
|
||||
if (message != null) throw ConfigurationException(message)
|
||||
}
|
||||
|
||||
return targetContainer ?: throw ConfigurationException("Invalid target specified")
|
||||
}
|
||||
|
||||
@Throws(ConfigurationException::class)
|
||||
private fun getMoveTarget(): KotlinMoveTarget {
|
||||
val target = getTargetContainerWithValidation()
|
||||
if (target is PsiDirectory) {
|
||||
val targetDir = target
|
||||
|
||||
val targetPackageFqName = getTargetPackageFqName(target)
|
||||
?: throw ConfigurationException("Cannot find target package name")
|
||||
|
||||
val suggestedName = KotlinNameSuggester.suggestNameByName(className) {
|
||||
targetDir.findFile(it + "." + KotlinFileType.EXTENSION) == null
|
||||
}
|
||||
|
||||
val targetFileName = suggestedName + "." + KotlinFileType.EXTENSION
|
||||
|
||||
return KotlinMoveTargetForDeferredFile(
|
||||
targetPackageFqName,
|
||||
targetDir, null
|
||||
) { createKotlinFile(targetFileName, targetDir, targetPackageFqName.asString()) }
|
||||
} else {
|
||||
return KotlinMoveTargetForExistingElement(target as KtElement)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(ConfigurationException::class)
|
||||
override fun computeModelResult() = computeModelResult(throwOnConflicts = false)
|
||||
|
||||
@Throws(ConfigurationException::class)
|
||||
override fun computeModelResult(throwOnConflicts: Boolean): MoveKotlinDeclarationsProcessor {
|
||||
|
||||
val moveTarget = getMoveTarget()
|
||||
|
||||
val outerInstanceParameterName = if (passOuterClass) packageName else null
|
||||
val delegate = MoveDeclarationsDelegate.NestedClass(className, outerInstanceParameterName)
|
||||
val moveDescriptor = MoveDeclarationsDescriptor(
|
||||
project,
|
||||
MoveSource(innerClass),
|
||||
moveTarget,
|
||||
delegate,
|
||||
searchInComments,
|
||||
isSearchInNonJavaFiles,
|
||||
deleteSourceFiles = false,
|
||||
moveCallback = null,
|
||||
openInEditor = isOpenInEditor
|
||||
)
|
||||
|
||||
return MoveKotlinDeclarationsProcessor(moveDescriptor, Mover.Default, throwOnConflicts)
|
||||
}
|
||||
}
|
||||
+10
-1
@@ -116,7 +116,7 @@
|
||||
<text value="&Update package directive"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="69aba" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<grid id="69aba" layout-manager="GridLayoutManager" row-count="3" 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>
|
||||
<grid row="5" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
@@ -151,6 +151,15 @@
|
||||
<text value="Search &references"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="cc60e" class="javax.swing.JCheckBox" binding="cbDeleteEmptySourceFiles">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<selected value="true"/>
|
||||
<text value="&Delete empty source files"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
|
||||
+108
-425
@@ -6,31 +6,20 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui;
|
||||
|
||||
import com.intellij.ide.util.DirectoryChooser;
|
||||
import com.intellij.ide.util.DirectoryUtil;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.event.DocumentEvent;
|
||||
import com.intellij.openapi.editor.event.DocumentListener;
|
||||
import com.intellij.openapi.fileTypes.FileTypeManager;
|
||||
import com.intellij.openapi.options.ConfigurationException;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.Pass;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.source.PsiFileImpl;
|
||||
import com.intellij.refactoring.*;
|
||||
import com.intellij.psi.PsiDirectory;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor;
|
||||
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.move.moveClassesOrPackages.AutocreatingSingleSourceRootMoveDestination;
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDestination;
|
||||
import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo;
|
||||
import com.intellij.refactoring.ui.RefactoringDialog;
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil;
|
||||
@@ -42,35 +31,29 @@ import com.intellij.util.ui.UIUtil;
|
||||
import kotlin.collections.CollectionsKt;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType;
|
||||
import org.jetbrains.kotlin.idea.core.PackageUtilsKt;
|
||||
import org.jetbrains.kotlin.idea.core.util.PhysicalFileSystemUtilsKt;
|
||||
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringBundle;
|
||||
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringSettings;
|
||||
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtilKt;
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo;
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionPanel;
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionTable;
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.MoveUtilsKt;
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*;
|
||||
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinDestinationFolderComboBox;
|
||||
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinFileChooserDialog;
|
||||
import org.jetbrains.kotlin.idea.util.application.ApplicationUtilsKt;
|
||||
import org.jetbrains.kotlin.name.FqName;
|
||||
import org.jetbrains.kotlin.psi.KtFile;
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration;
|
||||
import org.jetbrains.kotlin.psi.KtPureElement;
|
||||
import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt;
|
||||
import static org.jetbrains.kotlin.idea.roots.ProjectRootUtilsKt.getSuitableDestinationSourceRoots;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static org.jetbrains.kotlin.idea.roots.ProjectRootUtilsKt.getSuitableDestinationSourceRoots;
|
||||
|
||||
public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
private static final String RECENTS_KEY = "MoveKotlinTopLevelDeclarationsDialog.RECENTS_KEY";
|
||||
@@ -89,9 +72,42 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
private JTextField tfFileNameInPackage;
|
||||
private JCheckBox cbSpecifyFileNameInPackage;
|
||||
private JCheckBox cbUpdatePackageDirective;
|
||||
private JCheckBox cbDeleteEmptySourceFiles;
|
||||
private JCheckBox cbSearchReferences;
|
||||
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) {
|
||||
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,
|
||||
@@ -100,7 +116,8 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
@Nullable KtFile targetFile,
|
||||
boolean moveToPackage,
|
||||
boolean searchInComments,
|
||||
boolean searchForTextOccurences,
|
||||
boolean searchForTextOccurrences,
|
||||
boolean deleteEmptySourceFiles,
|
||||
@Nullable MoveCallback moveCallback
|
||||
) {
|
||||
super(project, true);
|
||||
@@ -114,7 +131,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
|
||||
setTitle(MoveHandler.REFACTORING_NAME);
|
||||
|
||||
initSearchOptions(searchInComments, searchForTextOccurences);
|
||||
initSearchOptions(searchInComments, searchForTextOccurrences, deleteEmptySourceFiles);
|
||||
|
||||
initPackageChooser(targetPackageName, targetDirectory, sourceFiles);
|
||||
|
||||
@@ -136,18 +153,6 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiDirectory getSourceDirectory(@NotNull Collection<KtFile> sourceFiles) {
|
||||
return CollectionsKt.single(
|
||||
CollectionsKt.distinct(
|
||||
CollectionsKt.map(
|
||||
sourceFiles,
|
||||
PsiFileImpl::getParent
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private static List<KtNamedDeclaration> getAllDeclarations(Collection<KtFile> sourceFiles) {
|
||||
return CollectionsKt.filterIsInstance(
|
||||
CollectionsKt.flatMap(
|
||||
@@ -165,30 +170,6 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<PsiFile> getFilesExistingInTargetDir(
|
||||
@NotNull List<KtFile> sourceFiles,
|
||||
@Nullable String targetFileName,
|
||||
@Nullable PsiDirectory targetDirectory
|
||||
) {
|
||||
if (targetDirectory == null) return emptyList();
|
||||
|
||||
List<String> fileNames =
|
||||
targetFileName != null
|
||||
? Collections.singletonList(targetFileName)
|
||||
: CollectionsKt.map(
|
||||
sourceFiles,
|
||||
PsiFileImpl::getName
|
||||
);
|
||||
|
||||
return CollectionsKt.filterNotNull(
|
||||
CollectionsKt.map(
|
||||
fileNames,
|
||||
targetDirectory::findFile
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void initMemberInfo(
|
||||
@NotNull Set<KtNamedDeclaration> elementsToMove,
|
||||
@NotNull List<KtFile> sourceFiles
|
||||
@@ -207,34 +188,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
memberInfoModel.memberInfoChanged(new MemberInfoChange<>(memberInfos));
|
||||
selectionPanel.getTable().setMemberInfoModel(memberInfoModel);
|
||||
selectionPanel.getTable().addMemberInfoChangeListener(memberInfoModel);
|
||||
selectionPanel.getTable().addMemberInfoChangeListener(
|
||||
new MemberInfoChangeListener<KtNamedDeclaration, KotlinMemberInfo>() {
|
||||
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) {
|
||||
updatePackageDirectiveCheckBox();
|
||||
updateFileNameInPackageField();
|
||||
// Update file name field only if it user hasn't changed it to some non-default value
|
||||
if (shouldUpdateFileNameField(event.getChangedMembers())) {
|
||||
updateSuggestedFileName();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
selectionPanel.getTable().addMemberInfoChangeListener(new MemberSelectionerInfoChangeListener(memberInfos));
|
||||
memberInfoPanel.add(selectionPanel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
@@ -243,8 +197,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
}
|
||||
|
||||
private void updateFileNameInPackageField() {
|
||||
boolean movingSingleFileToPackage = isMoveToPackage()
|
||||
&& getSourceFiles(getSelectedElementsToMove()).size() == 1;
|
||||
boolean movingSingleFileToPackage = rbMoveToPackage.isSelected() && getSourceFiles(getSelectedElementsToMove()).size() == 1;
|
||||
cbSpecifyFileNameInPackage.setEnabled(movingSingleFileToPackage);
|
||||
tfFileNameInPackage.setEnabled(movingSingleFileToPackage && cbSpecifyFileNameInPackage.isSelected());
|
||||
}
|
||||
@@ -275,9 +228,10 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
cbUpdatePackageDirective.setSelected(arePackagesAndDirectoryMatched(sourceFiles));
|
||||
}
|
||||
|
||||
private void initSearchOptions(boolean searchInComments, boolean searchForTextOccurences) {
|
||||
private void initSearchOptions(boolean searchInComments, boolean searchForTextOccurences, boolean deleteEmptySourceFiles) {
|
||||
cbSearchInComments.setSelected(searchInComments);
|
||||
cbSearchTextOccurrences.setSelected(searchForTextOccurences);
|
||||
cbDeleteEmptySourceFiles.setSelected(deleteEmptySourceFiles);
|
||||
}
|
||||
|
||||
private void initMoveToButtons(boolean moveToPackage) {
|
||||
@@ -309,13 +263,14 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
@NotNull List<KtFile> sourceFiles
|
||||
) {
|
||||
PsiDirectory sourceDir = sourceFiles.get(0).getParent();
|
||||
assert sourceDir != null : sourceFiles.get(0).getVirtualFile().getPath();
|
||||
if (sourceDir == null) {
|
||||
throw new AssertionError("File chooser initialization failed");
|
||||
}
|
||||
|
||||
fileChooser.addActionListener(
|
||||
e -> {
|
||||
fileChooser.addActionListener(e -> {
|
||||
KotlinFileChooserDialog dialog = new KotlinFileChooserDialog("Choose Containing File", myProject);
|
||||
|
||||
File targetFile1 = new File(getTargetFilePath());
|
||||
File targetFile1 = new File(fileChooser.getText());
|
||||
PsiFile targetPsiFile = PhysicalFileSystemUtilsKt.toPsiFile(targetFile1, myProject);
|
||||
if (targetPsiFile instanceof KtFile) {
|
||||
dialog.select((KtFile) targetPsiFile);
|
||||
@@ -336,12 +291,9 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
}
|
||||
);
|
||||
|
||||
String initialTargetPath =
|
||||
targetFile != null
|
||||
String initialTargetPath = targetFile != null
|
||||
? targetFile.getVirtualFile().getPath()
|
||||
: sourceFiles.get(0).getVirtualFile().getParent().getPath() +
|
||||
"/" +
|
||||
MoveUtilsKt.guessNewFileName(elementsToMove);
|
||||
: sourceFiles.get(0).getVirtualFile().getParent().getPath() + "/" + MoveUtilsKt.guessNewFileName(elementsToMove);
|
||||
fileChooser.setText(initialTargetPath);
|
||||
}
|
||||
|
||||
@@ -357,28 +309,23 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
}
|
||||
|
||||
private ReferenceEditorComboWithBrowseButton createPackageChooser() {
|
||||
ReferenceEditorComboWithBrowseButton packageChooser =
|
||||
new PackageNameReferenceEditorCombo("", myProject, RECENTS_KEY, RefactoringBundle.message("choose.destination.package"));
|
||||
Document document = packageChooser.getChildComponent().getDocument();
|
||||
document.addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void documentChanged(@NotNull DocumentEvent e) {
|
||||
validateButtons();
|
||||
}
|
||||
});
|
||||
|
||||
return packageChooser;
|
||||
return new PackageNameReferenceEditorCombo(
|
||||
"",
|
||||
myProject,
|
||||
RECENTS_KEY,
|
||||
RefactoringBundle.message("choose.destination.package")
|
||||
);
|
||||
}
|
||||
|
||||
private void updateControls() {
|
||||
boolean moveToPackage = isMoveToPackage();
|
||||
boolean moveToPackage = rbMoveToPackage.isSelected();
|
||||
classPackageChooser.setEnabled(moveToPackage);
|
||||
updateFileNameInPackageField();
|
||||
fileChooser.setEnabled(!moveToPackage);
|
||||
updatePackageDirectiveCheckBox();
|
||||
UIUtil.setEnabled(targetPanel, moveToPackage && hasAnySourceRoots(), true);
|
||||
updateSuggestedFileName();
|
||||
validateButtons();
|
||||
myHelpAction.setEnabled(false);
|
||||
}
|
||||
|
||||
private boolean isFullFileMove() {
|
||||
@@ -393,7 +340,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
}
|
||||
|
||||
private void updatePackageDirectiveCheckBox() {
|
||||
cbUpdatePackageDirective.setEnabled(isMoveToPackage() && isFullFileMove());
|
||||
cbUpdatePackageDirective.setEnabled(rbMoveToPackage.isSelected() && isFullFileMove());
|
||||
}
|
||||
|
||||
private boolean hasAnySourceRoots() {
|
||||
@@ -402,206 +349,12 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
|
||||
private void saveRefactoringSettings() {
|
||||
KotlinRefactoringSettings refactoringSettings = KotlinRefactoringSettings.getInstance();
|
||||
refactoringSettings.MOVE_SEARCH_IN_COMMENTS = isSearchInComments();
|
||||
refactoringSettings.MOVE_SEARCH_FOR_TEXT = isSearchInNonJavaFiles();
|
||||
refactoringSettings.MOVE_SEARCH_IN_COMMENTS = cbSearchInComments.isSelected();
|
||||
refactoringSettings.MOVE_SEARCH_FOR_TEXT = cbSearchTextOccurrences.isSelected();
|
||||
refactoringSettings.MOVE_DELETE_EMPTY_SOURCE_FILES = cbDeleteEmptySourceFiles.isSelected();
|
||||
refactoringSettings.MOVE_PREVIEW_USAGES = isPreviewUsages();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Pair<VirtualFile, ? extends MoveDestination> selectPackageBasedTargetDirAndDestination(boolean askIfDoesNotExist) {
|
||||
String packageName = getTargetPackage();
|
||||
|
||||
RecentsManager.getInstance(myProject).registerRecentEntry(RECENTS_KEY, packageName);
|
||||
PackageWrapper targetPackage = new PackageWrapper(PsiManager.getInstance(myProject), packageName);
|
||||
if (!targetPackage.exists() && askIfDoesNotExist) {
|
||||
int ret = Messages.showYesNoDialog(myProject, RefactoringBundle.message("package.does.not.exist", packageName),
|
||||
RefactoringBundle.message("move.title"), Messages.getQuestionIcon());
|
||||
if (ret != Messages.YES) return null;
|
||||
}
|
||||
|
||||
DirectoryChooser.ItemWrapper selectedItem = (DirectoryChooser.ItemWrapper) destinationFolderCB.getComboBox().getSelectedItem();
|
||||
PsiDirectory selectedPsiDirectory = selectedItem != null ? selectedItem.getDirectory() : null;
|
||||
if (selectedPsiDirectory == null) {
|
||||
if (initialTargetDirectory != null) {
|
||||
selectedPsiDirectory = initialTargetDirectory;
|
||||
}
|
||||
else {
|
||||
return Pair.create(null, new MultipleRootsMoveDestination(targetPackage));
|
||||
}
|
||||
}
|
||||
|
||||
VirtualFile targetDirectory = selectedPsiDirectory.getVirtualFile();
|
||||
return Pair.create(targetDirectory, new AutocreatingSingleSourceRootMoveDestination(targetPackage, targetDirectory));
|
||||
}
|
||||
|
||||
private boolean checkTargetFileName(String fileName) {
|
||||
if (FileTypeManager.getInstance().getFileTypeByFileName(fileName) == KotlinFileType.INSTANCE) return true;
|
||||
setErrorText("Can't move to non-Kotlin file");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private KotlinMoveTarget selectMoveTarget() {
|
||||
String message = verifyBeforeRun();
|
||||
if (message != null) {
|
||||
setErrorText(message);
|
||||
return null;
|
||||
}
|
||||
|
||||
setErrorText(null);
|
||||
|
||||
List<KtFile> sourceFiles = getSourceFiles(getSelectedElementsToMove());
|
||||
PsiDirectory sourceDirectory = getSourceDirectory(sourceFiles);
|
||||
|
||||
if (isMoveToPackage()) {
|
||||
Pair<VirtualFile, ? extends MoveDestination> targetDirWithMoveDestination = selectPackageBasedTargetDirAndDestination(true);
|
||||
if (targetDirWithMoveDestination == null) return null;
|
||||
|
||||
VirtualFile targetDir = targetDirWithMoveDestination.getFirst();
|
||||
MoveDestination moveDestination = targetDirWithMoveDestination.getSecond();
|
||||
|
||||
String targetFileName = sourceFiles.size() > 1 ? null : tfFileNameInPackage.getText();
|
||||
if (targetFileName != null && !checkTargetFileName(targetFileName)) return null;
|
||||
|
||||
PsiDirectory targetDirectory = moveDestination.getTargetIfExists(sourceDirectory);
|
||||
|
||||
List<PsiFile> filesExistingInTargetDir = getFilesExistingInTargetDir(sourceFiles, targetFileName, targetDirectory);
|
||||
if (!filesExistingInTargetDir.isEmpty()) {
|
||||
if (filesExistingInTargetDir.size() > 1) {
|
||||
String filePathsToReport = StringUtil.join(
|
||||
filesExistingInTargetDir,
|
||||
file -> 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;
|
||||
}
|
||||
|
||||
PsiFile targetFile = filesExistingInTargetDir.get(0);
|
||||
|
||||
if (!sourceFiles.contains(targetFile)) {
|
||||
String question = String.format(
|
||||
"File '%s' already exists. Do you want to move selected declarations to this file?",
|
||||
targetFile.getVirtualFile().getPath()
|
||||
);
|
||||
int ret =
|
||||
Messages.showYesNoDialog(myProject, question, RefactoringBundle.message("move.title"),
|
||||
Messages.getQuestionIcon());
|
||||
if (ret != Messages.YES) return null;
|
||||
}
|
||||
|
||||
if (targetFile instanceof KtFile) {
|
||||
return new KotlinMoveTargetForExistingElement((KtFile) targetFile);
|
||||
}
|
||||
}
|
||||
|
||||
// All source files must be in the same directory
|
||||
return new KotlinMoveTargetForDeferredFile(
|
||||
new FqName(getTargetPackage()),
|
||||
moveDestination.getTargetIfExists(sourceFiles.get(0)),
|
||||
targetDir,
|
||||
originalFile -> KotlinRefactoringUtilKt.getOrCreateKotlinFile(
|
||||
targetFileName != null ? targetFileName : originalFile.getName(),
|
||||
moveDestination.getTargetDirectory(originalFile)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
File targetFile = new File(getTargetFilePath());
|
||||
if (!checkTargetFileName(targetFile.getName())) return null;
|
||||
KtFile jetFile = (KtFile) PhysicalFileSystemUtilsKt.toPsiFile(targetFile, myProject);
|
||||
if (jetFile != null) {
|
||||
if (sourceFiles.size() == 1 && sourceFiles.contains(jetFile)) {
|
||||
setErrorText("Can't move to the original file");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new KotlinMoveTargetForExistingElement(jetFile);
|
||||
}
|
||||
|
||||
Path targetFilePath = targetFile.toPath();
|
||||
Path targetDirPath = targetFilePath.getParent();
|
||||
if (targetDirPath == null || !targetDirPath.startsWith(Objects.requireNonNull(getProject().getBasePath()))) {
|
||||
setErrorText("Incorrect target path. Directory " + targetDirPath + " does not belong to current project.");
|
||||
return null;
|
||||
}
|
||||
if (PhysicalFileSystemUtilsKt.toPsiDirectory(targetDirPath.toFile(), myProject) == null) {
|
||||
int ret = Messages.showYesNoDialog(
|
||||
myProject,
|
||||
"You are about to move all declarations to the directory that does not exist. Do you want to create it?",
|
||||
RefactoringBundle.message("move.title"),
|
||||
Messages.getQuestionIcon()
|
||||
);
|
||||
if (ret == Messages.YES) {
|
||||
try {
|
||||
ApplicationManager.getApplication().runWriteAction(() -> {
|
||||
DirectoryUtil.mkdirs(PsiManager.getInstance(getProject()), targetDirPath.toString());
|
||||
});
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
setErrorText("Failed to create parent directory: " + targetDirPath);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File targetDir = targetDirPath.toFile();
|
||||
PsiDirectory psiDirectory = targetDir != null ? PhysicalFileSystemUtilsKt.toPsiDirectory(targetDir, myProject) : null;
|
||||
if (psiDirectory == null) {
|
||||
setErrorText("No directory found for file: " + targetFile.getPath());
|
||||
return null;
|
||||
}
|
||||
|
||||
Set<FqName> sourcePackageFqNames = CollectionsKt.mapTo(
|
||||
sourceFiles,
|
||||
new LinkedHashSet<>(),
|
||||
KtFile::getPackageFqName
|
||||
);
|
||||
FqName targetPackageFqName = CollectionsKt.singleOrNull(sourcePackageFqNames);
|
||||
if (targetPackageFqName == null) {
|
||||
PsiPackage psiPackage = JavaDirectoryService.getInstance().getPackage(psiDirectory);
|
||||
if (psiPackage == null) {
|
||||
setErrorText("Could not find package corresponding to " + targetDir.getPath());
|
||||
return null;
|
||||
}
|
||||
targetPackageFqName = new FqName(psiPackage.getQualifiedName());
|
||||
}
|
||||
|
||||
String finalTargetPackageFqName = targetPackageFqName.asString();
|
||||
return new KotlinMoveTargetForDeferredFile(
|
||||
targetPackageFqName,
|
||||
psiDirectory,
|
||||
null,
|
||||
originalFile -> KotlinRefactoringUtilKt.getOrCreateKotlinFile(targetFile.getName(), psiDirectory, finalTargetPackageFqName)
|
||||
);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String verifyBeforeRun() {
|
||||
if (memberTable.getSelectedMemberInfos().isEmpty()) return "At least one member must be selected";
|
||||
|
||||
if (isMoveToPackage()) {
|
||||
String name = getTargetPackage();
|
||||
if (name.length() != 0 && !PsiNameHelper.getInstance(myProject).isQualifiedName(name)) {
|
||||
return "\'" + name + "\' is invalid destination package name";
|
||||
}
|
||||
}
|
||||
else {
|
||||
PsiFile targetFile = PhysicalFileSystemUtilsKt.toPsiFile(new File(getTargetFilePath()), myProject);
|
||||
if (!(targetFile == null || targetFile instanceof KtFile)) {
|
||||
return KotlinRefactoringBundle.message("refactoring.move.non.kotlin.file");
|
||||
}
|
||||
}
|
||||
|
||||
if (getSourceFiles(getSelectedElementsToMove()).size() == 1 && tfFileNameInPackage.getText().isEmpty()) {
|
||||
return "File name may not be empty";
|
||||
}
|
||||
|
||||
return null;
|
||||
RecentsManager.getInstance(myProject).registerRecentEntry(RECENTS_KEY, getTargetPackage());
|
||||
}
|
||||
|
||||
private List<KtNamedDeclaration> getSelectedElementsToMove() {
|
||||
@@ -621,136 +374,66 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
return "#" + getClass().getName();
|
||||
}
|
||||
|
||||
protected final String getTargetPackage() {
|
||||
private String getTargetPackage() {
|
||||
return classPackageChooser.getText().trim();
|
||||
}
|
||||
|
||||
protected final String getTargetFilePath() {
|
||||
return fileChooser.getText();
|
||||
private List<KtNamedDeclaration> getSelectedElementsToMoveChecked() throws ConfigurationException {
|
||||
List<KtNamedDeclaration> elementsToMove = getSelectedElementsToMove();
|
||||
if (elementsToMove.isEmpty()) {
|
||||
throw new ConfigurationException("No elements to move are selected");
|
||||
}
|
||||
return elementsToMove;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void canRun() throws ConfigurationException {
|
||||
String message = verifyBeforeRun();
|
||||
if (message != null) {
|
||||
throw new ConfigurationException(message);
|
||||
}
|
||||
private Model<BaseRefactoringProcessor> getModel() throws ConfigurationException {
|
||||
|
||||
DirectoryChooser.ItemWrapper selectedItem = (DirectoryChooser.ItemWrapper) destinationFolderCB.getComboBox().getSelectedItem();
|
||||
PsiDirectory selectedPsiDirectory = selectedItem != null ? selectedItem.getDirectory() : initialTargetDirectory;
|
||||
|
||||
return new MoveKotlinTopLevelDeclarationsModel(
|
||||
myProject,
|
||||
getSelectedElementsToMoveChecked(),
|
||||
getTargetPackage(),
|
||||
selectedPsiDirectory,
|
||||
tfFileNameInPackage.getText(),
|
||||
fileChooser.getText(),
|
||||
rbMoveToPackage.isSelected(),
|
||||
cbSearchReferences.isSelected(),
|
||||
cbSearchInComments.isSelected(),
|
||||
cbSearchTextOccurrences.isSelected(),
|
||||
cbDeleteEmptySourceFiles.isSelected(),
|
||||
cbUpdatePackageDirective.isSelected(),
|
||||
isFullFileMove(),
|
||||
moveCallback
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doAction() {
|
||||
KotlinMoveTarget target = selectMoveTarget();
|
||||
if (target == null) return;
|
||||
|
||||
BaseRefactoringProcessor processor;
|
||||
try {
|
||||
processor = getModel().computeModelResult();
|
||||
}
|
||||
catch (ConfigurationException e) {
|
||||
setErrorText(e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
saveRefactoringSettings();
|
||||
|
||||
List<KtNamedDeclaration> elementsToMove = getSelectedElementsToMove();
|
||||
List<KtFile> sourceFiles = getSourceFiles(elementsToMove);
|
||||
PsiDirectory sourceDirectory = getSourceDirectory(sourceFiles);
|
||||
|
||||
for (PsiElement element : elementsToMove) {
|
||||
String message = target.verify(element.getContainingFile());
|
||||
if (message != null) {
|
||||
CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), message, null, myProject);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
boolean deleteSourceFile = false;
|
||||
|
||||
if (isFullFileMove()) {
|
||||
if (isMoveToPackage()) {
|
||||
Pair<VirtualFile, ? extends MoveDestination> sourceRootWithMoveDestination =
|
||||
selectPackageBasedTargetDirAndDestination(false);
|
||||
//noinspection ConstantConditions
|
||||
MoveDestination moveDestination = sourceRootWithMoveDestination.getSecond();
|
||||
|
||||
PsiDirectory targetDir = moveDestination.getTargetIfExists(sourceDirectory);
|
||||
String targetFileName = sourceFiles.size() > 1 ? null : tfFileNameInPackage.getText();
|
||||
List<PsiFile> filesExistingInTargetDir = getFilesExistingInTargetDir(sourceFiles, targetFileName, targetDir);
|
||||
if (filesExistingInTargetDir.isEmpty()
|
||||
|| (filesExistingInTargetDir.size() == 1 && sourceFiles.contains(filesExistingInTargetDir.get(0)))) {
|
||||
PsiDirectory targetDirectory = ApplicationUtilsKt.runWriteAction(
|
||||
() -> moveDestination.getTargetDirectory(sourceDirectory)
|
||||
);
|
||||
|
||||
for (KtFile sourceFile : sourceFiles) {
|
||||
MoveUtilsKt.setUpdatePackageDirective(sourceFile, cbUpdatePackageDirective.isSelected());
|
||||
}
|
||||
|
||||
BaseRefactoringProcessor processor;
|
||||
processor = sourceFiles.size() == 1 && targetFileName != null
|
||||
? new MoveToKotlinFileProcessor(myProject,
|
||||
CollectionsKt.single(sourceFiles),
|
||||
targetDirectory,
|
||||
targetFileName,
|
||||
isSearchInComments(),
|
||||
isSearchInNonJavaFiles(),
|
||||
moveCallback)
|
||||
: new KotlinAwareMoveFilesOrDirectoriesProcessor(myProject,
|
||||
sourceFiles,
|
||||
targetDirectory,
|
||||
cbSearchReferences.isSelected(),
|
||||
isSearchInComments(),
|
||||
isSearchInNonJavaFiles(),
|
||||
moveCallback);
|
||||
|
||||
invokeRefactoring(processor);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
MoveDeclarationsDescriptor options = new MoveDeclarationsDescriptor(
|
||||
myProject,
|
||||
MoveKotlinDeclarationsProcessorKt.MoveSource(elementsToMove),
|
||||
target,
|
||||
MoveDeclarationsDelegate.TopLevel.INSTANCE,
|
||||
isSearchInComments(),
|
||||
isSearchInNonJavaFiles(),
|
||||
deleteSourceFile,
|
||||
moveCallback,
|
||||
false,
|
||||
null,
|
||||
true,
|
||||
cbSearchReferences.isSelected()
|
||||
);
|
||||
invokeRefactoring(new MoveKotlinDeclarationsProcessor(options, Mover.Default.INSTANCE));
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
invokeRefactoring(processor);
|
||||
} catch (IncorrectOperationException e) {
|
||||
CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), e.getMessage(), null, myProject);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSearchInNonJavaFiles() {
|
||||
return cbSearchTextOccurrences.isSelected();
|
||||
}
|
||||
|
||||
private boolean isSearchInComments() {
|
||||
return cbSearchInComments.isSelected();
|
||||
}
|
||||
|
||||
private boolean isMoveToPackage() {
|
||||
return rbMoveToPackage.isSelected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return classPackageChooser.getChildComponent();
|
||||
}
|
||||
|
||||
private static class MemberInfoModelImpl extends AbstractMemberInfoModel<KtNamedDeclaration, KotlinMemberInfo> {
|
||||
|
||||
}
|
||||
private static class MemberInfoModelImpl extends AbstractMemberInfoModel<KtNamedDeclaration, KotlinMemberInfo> { }
|
||||
}
|
||||
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui
|
||||
|
||||
import com.intellij.openapi.fileTypes.FileTypeManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.options.ConfigurationException
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor
|
||||
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.moveClassesOrPackages.AutocreatingSingleSourceRootMoveDestination
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDestination
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.core.util.toPsiFile
|
||||
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringBundle
|
||||
import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.getOrCreateDirectory
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.updatePackageDirective
|
||||
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.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||
import java.io.File
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.nio.file.Paths
|
||||
|
||||
internal class MoveKotlinTopLevelDeclarationsModel(
|
||||
val project: Project,
|
||||
val elementsToMove: List<KtNamedDeclaration>,
|
||||
val targetPackage: String,
|
||||
val selectedPsiDirectory: PsiDirectory?,
|
||||
val fileNameInPackage: String,
|
||||
val targetFilePath: String,
|
||||
val isMoveToPackage: Boolean,
|
||||
val isSearchReferences: Boolean,
|
||||
val isSearchInComments: Boolean,
|
||||
val isSearchInNonJavaFiles: Boolean,
|
||||
val isDeleteEmptyFiles: Boolean,
|
||||
val isUpdatePackageDirective: Boolean,
|
||||
val isFullFileMove: Boolean,
|
||||
val moveCallback: MoveCallback?
|
||||
) : Model<BaseRefactoringProcessor> {
|
||||
|
||||
private val sourceDirectory by lazy {
|
||||
sourceFiles.singleOrNull { it.parent !== null }?.parent ?: throw ConfigurationException("Can't determine sources directory")
|
||||
}
|
||||
|
||||
private val sourceFiles = elementsToMove.map { it.containingKtFile }.distinct()
|
||||
|
||||
private data class TargetDirAndDestination(val targetDir: VirtualFile?, val destination: MoveDestination)
|
||||
|
||||
private fun selectPackageBasedTargetDirAndDestination(): TargetDirAndDestination {
|
||||
|
||||
val targetPackageWrapper = PackageWrapper(PsiManager.getInstance(project), targetPackage)
|
||||
|
||||
return if (selectedPsiDirectory === null)
|
||||
TargetDirAndDestination(null, MultipleRootsMoveDestination(targetPackageWrapper))
|
||||
else {
|
||||
TargetDirAndDestination(
|
||||
selectedPsiDirectory.virtualFile,
|
||||
AutocreatingSingleSourceRootMoveDestination(targetPackageWrapper, selectedPsiDirectory.virtualFile)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkTargetFileName(fileName: String) {
|
||||
if (FileTypeManager.getInstance().getFileTypeByFileName(fileName) != KotlinFileType.INSTANCE) {
|
||||
throw ConfigurationException(KotlinRefactoringBundle.message("refactoring.move.non.kotlin.file"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFilesExistingInTargetDir(
|
||||
targetFileName: String?,
|
||||
targetDirectory: PsiDirectory?
|
||||
): List<PsiFile> {
|
||||
targetDirectory ?: return emptyList()
|
||||
|
||||
val fileNames = targetFileName?.let { listOf(it) } ?: sourceFiles.map { it.name }
|
||||
|
||||
return fileNames
|
||||
.distinct()
|
||||
.mapNotNull { targetDirectory.findFile(it) }
|
||||
}
|
||||
|
||||
private fun selectMoveTargetToPackage(): KotlinMoveTarget {
|
||||
|
||||
if (sourceFiles.size > 1) throw ConfigurationException("Can't move from multiply source files")
|
||||
|
||||
checkTargetFileName(fileNameInPackage)
|
||||
|
||||
val (targetDir, moveDestination) = selectPackageBasedTargetDirAndDestination()
|
||||
|
||||
val targetDirectory = moveDestination.getTargetIfExists(sourceDirectory)
|
||||
|
||||
val filesExistingInTargetDir = getFilesExistingInTargetDir(fileNameInPackage, targetDirectory)
|
||||
if (filesExistingInTargetDir.isNotEmpty()) {
|
||||
if (filesExistingInTargetDir.size > 1) {
|
||||
val filePathsToReport = filesExistingInTargetDir.joinToString(
|
||||
separator = "\n",
|
||||
prefix = "Cannot perform refactoring since the following files already exist:\n\n"
|
||||
) { it.virtualFile.path }
|
||||
throw ConfigurationException(filePathsToReport)
|
||||
}
|
||||
|
||||
(filesExistingInTargetDir[0] as? KtFile)?.let {
|
||||
return KotlinMoveTargetForExistingElement(it)
|
||||
}
|
||||
}
|
||||
|
||||
// All source files must be in the same directory
|
||||
return KotlinMoveTargetForDeferredFile(
|
||||
FqName(targetPackage),
|
||||
moveDestination.getTargetIfExists(sourceFiles[0]),
|
||||
targetDir
|
||||
) { getOrCreateKotlinFile(fileNameInPackage, moveDestination.getTargetDirectory(it)) }
|
||||
}
|
||||
|
||||
private fun selectMoveTargetToFile(): KotlinMoveTarget {
|
||||
|
||||
try {
|
||||
Paths.get(targetFilePath)
|
||||
} catch (e: InvalidPathException) {
|
||||
throw ConfigurationException("Invalid target path $targetFilePath")
|
||||
}
|
||||
|
||||
val targetFile = File(targetFilePath)
|
||||
checkTargetFileName(targetFile.name)
|
||||
|
||||
val jetFile = targetFile.toPsiFile(project) as? KtFile
|
||||
if (jetFile !== null) {
|
||||
if (sourceFiles.size == 1 && sourceFiles.contains(jetFile)) {
|
||||
throw ConfigurationException("Can't move to the original file")
|
||||
}
|
||||
return KotlinMoveTargetForExistingElement(jetFile)
|
||||
}
|
||||
|
||||
val targetDirPath = targetFile.toPath().parent
|
||||
val projectBasePath = project.basePath ?: throw ConfigurationException("Can't move for current project")
|
||||
if (targetDirPath === null || !targetDirPath.startsWith(projectBasePath)) {
|
||||
throw ConfigurationException("Incorrect target path. Directory $targetDirPath does not belong to current project.")
|
||||
}
|
||||
|
||||
val absoluteTargetDirPath = targetDirPath.toString()
|
||||
val psiDirectory: PsiDirectory
|
||||
try {
|
||||
psiDirectory = getOrCreateDirectory(absoluteTargetDirPath, project)
|
||||
} catch (e: IncorrectOperationException) {
|
||||
throw ConfigurationException("Failed to create parent directory: $absoluteTargetDirPath")
|
||||
}
|
||||
|
||||
val targetPackageFqName = sourceFiles.singleOrNull()?.packageFqName
|
||||
?: JavaDirectoryService.getInstance().getPackage(psiDirectory)?.let { FqName(it.qualifiedName) }
|
||||
?: throw ConfigurationException("Could not find package corresponding to $absoluteTargetDirPath")
|
||||
|
||||
val finalTargetPackageFqName = targetPackageFqName.asString()
|
||||
return KotlinMoveTargetForDeferredFile(
|
||||
targetPackageFqName,
|
||||
psiDirectory,
|
||||
targetFile = null
|
||||
) { getOrCreateKotlinFile(targetFile.name, psiDirectory, finalTargetPackageFqName) }
|
||||
}
|
||||
|
||||
private fun selectMoveTarget() =
|
||||
if (isMoveToPackage) selectMoveTargetToPackage() else selectMoveTargetToFile()
|
||||
|
||||
private fun verifyBeforeRun(target: KotlinMoveTarget) {
|
||||
|
||||
if (elementsToMove.isEmpty()) throw ConfigurationException("At least one member must be selected")
|
||||
|
||||
for (element in elementsToMove) {
|
||||
target.verify(element.containingFile)?.let { throw ConfigurationException(it) }
|
||||
}
|
||||
|
||||
if (isMoveToPackage) {
|
||||
if (targetPackage.isNotEmpty() && !PsiNameHelper.getInstance(project).isQualifiedName(targetPackage)) {
|
||||
throw ConfigurationException("\'$targetPackage\' is invalid destination package name")
|
||||
}
|
||||
} else {
|
||||
val targetFile = File(targetFilePath).toPsiFile(project)
|
||||
if (targetFile !== null && targetFile !is KtFile) {
|
||||
throw ConfigurationException(KotlinRefactoringBundle.message("refactoring.move.non.kotlin.file"))
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceFiles.size == 1 && fileNameInPackage.isEmpty()) {
|
||||
throw ConfigurationException("File name may not be empty")
|
||||
}
|
||||
}
|
||||
|
||||
private val verifiedMoveTarget get() = selectMoveTarget().also { verifyBeforeRun(it) }
|
||||
|
||||
@Throws(ConfigurationException::class)
|
||||
override fun computeModelResult() = computeModelResult(throwOnConflicts = false)
|
||||
|
||||
@Throws(ConfigurationException::class)
|
||||
override fun computeModelResult(throwOnConflicts: Boolean): BaseRefactoringProcessor {
|
||||
|
||||
val target = verifiedMoveTarget
|
||||
|
||||
if (isFullFileMove && isMoveToPackage) {
|
||||
val (_, moveDestination) = selectPackageBasedTargetDirAndDestination()
|
||||
|
||||
val targetDir = moveDestination.getTargetIfExists(sourceDirectory)
|
||||
val targetFileName = if (sourceFiles.size > 1) null else fileNameInPackage
|
||||
|
||||
val filesExistingInTargetDir = getFilesExistingInTargetDir(targetFileName, targetDir)
|
||||
|
||||
if (filesExistingInTargetDir.isEmpty() ||
|
||||
(filesExistingInTargetDir.size == 1 && sourceFiles.contains(filesExistingInTargetDir[0]))
|
||||
) {
|
||||
val targetDirectory = project.executeCommand(RefactoringBundle.message("move.title"), null) {
|
||||
runWriteAction<PsiDirectory> {
|
||||
moveDestination.getTargetDirectory(sourceDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
sourceFiles.forEach { it.updatePackageDirective = isUpdatePackageDirective }
|
||||
|
||||
return if (sourceFiles.size == 1 && targetFileName !== null)
|
||||
MoveToKotlinFileProcessor(
|
||||
project,
|
||||
sourceFiles[0],
|
||||
targetDirectory,
|
||||
targetFileName,
|
||||
searchInComments = isSearchInComments,
|
||||
searchInNonJavaFiles = isSearchInNonJavaFiles,
|
||||
moveCallback = moveCallback,
|
||||
throwOnConflicts = throwOnConflicts
|
||||
)
|
||||
else
|
||||
KotlinAwareMoveFilesOrDirectoriesProcessor(
|
||||
project,
|
||||
sourceFiles,
|
||||
targetDirectory,
|
||||
isSearchReferences,
|
||||
searchInComments = isSearchInComments,
|
||||
searchInNonJavaFiles = isSearchInNonJavaFiles,
|
||||
moveCallback = moveCallback,
|
||||
throwOnConflicts = throwOnConflicts
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val options = MoveDeclarationsDescriptor(
|
||||
project,
|
||||
MoveSource(elementsToMove),
|
||||
target,
|
||||
MoveDeclarationsDelegate.TopLevel,
|
||||
isSearchInComments,
|
||||
isSearchInNonJavaFiles,
|
||||
deleteSourceFiles = isFullFileMove && isDeleteEmptyFiles,
|
||||
moveCallback = moveCallback,
|
||||
openInEditor = false,
|
||||
allElementsToMove = null,
|
||||
analyzeConflicts = true,
|
||||
searchReferences = isSearchReferences
|
||||
)
|
||||
return MoveKotlinDeclarationsProcessor(options, Mover.Default, throwOnConflicts)
|
||||
}
|
||||
}
|
||||
+30
-7
@@ -7,13 +7,18 @@ package org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.actionSystem.LangDataKeys
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.refactoring.move.MoveCallback
|
||||
import com.intellij.refactoring.move.MoveHandler
|
||||
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesHandler
|
||||
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.KotlinAwareMoveFilesOrDirectoriesDialog
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.KotlinAwareMoveFilesOrDirectoriesModel
|
||||
import org.jetbrains.kotlin.idea.util.application.executeCommand
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
@@ -43,12 +48,30 @@ class KotlinMoveFilesOrDirectoriesHandler : MoveFilesOrDirectoriesHandler() {
|
||||
override fun doMove(project: Project, elements: Array<out PsiElement>, targetContainer: PsiElement?, callback: MoveCallback?) {
|
||||
if (!(targetContainer == null || targetContainer is PsiDirectory || targetContainer is PsiDirectoryContainer)) return
|
||||
|
||||
moveFilesOrDirectories(
|
||||
project,
|
||||
adjustForMove(project, elements, targetContainer) ?: return,
|
||||
targetContainer,
|
||||
callback?.let { MoveCallback { it.refactoringCompleted() } }
|
||||
)
|
||||
val adjustedElementsToMove = adjustForMove(project, elements, targetContainer)?.toList() ?: return
|
||||
|
||||
val targetDirectory = MoveFilesOrDirectoriesUtil.resolveToDirectory(project, targetContainer)
|
||||
if (targetContainer != null && targetDirectory == null) return
|
||||
|
||||
val initialTargetDirectory = MoveFilesOrDirectoriesUtil.getInitialTargetDirectory(targetDirectory, elements)
|
||||
|
||||
if (ApplicationManager.getApplication().isUnitTestMode && initialTargetDirectory !== null) {
|
||||
KotlinAwareMoveFilesOrDirectoriesModel(
|
||||
project,
|
||||
adjustedElementsToMove,
|
||||
initialTargetDirectory.virtualFile.presentableUrl,
|
||||
updatePackageDirective = true,
|
||||
searchReferences = true,
|
||||
moveCallback = callback
|
||||
).run {
|
||||
project.executeCommand(MoveHandler.REFACTORING_NAME) {
|
||||
computeModelResult().run()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
KotlinAwareMoveFilesOrDirectoriesDialog(project, initialTargetDirectory, elements, callback).show()
|
||||
}
|
||||
|
||||
override fun tryToMove(
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.DialogWrapper
|
||||
import com.intellij.psi.PsiDirectory
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.refactoring.RefactoringBundle
|
||||
import com.intellij.refactoring.copy.CopyFilesOrDirectoriesHandler
|
||||
import com.intellij.refactoring.move.MoveCallback
|
||||
import com.intellij.refactoring.move.MoveHandler
|
||||
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import org.jetbrains.kotlin.idea.refactoring.isInKotlinAwareSourceRoot
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.KotlinAwareMoveFilesOrDirectoriesProcessor
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.KotlinAwareMoveFilesOrDirectoriesDialog
|
||||
import org.jetbrains.kotlin.idea.util.application.executeCommand
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
fun invokeMoveFilesOrDirectoriesRefactoring(
|
||||
moveDialog: KotlinAwareMoveFilesOrDirectoriesDialog?,
|
||||
project: Project,
|
||||
elements: Array<out PsiElement>,
|
||||
initialTargetDirectory: PsiDirectory?,
|
||||
moveCallback: MoveCallback?
|
||||
) {
|
||||
fun closeDialog() {
|
||||
moveDialog?.close(DialogWrapper.CANCEL_EXIT_CODE)
|
||||
}
|
||||
|
||||
project.executeCommand(MoveHandler.REFACTORING_NAME) {
|
||||
val selectedDir = (if (moveDialog != null) moveDialog.targetDirectory else initialTargetDirectory) ?: return@executeCommand
|
||||
val updatePackageDirective = moveDialog?.updatePackageDirective
|
||||
|
||||
try {
|
||||
val choice = if (elements.size > 1 || elements[0] is PsiDirectory) intArrayOf(-1) else null
|
||||
val elementsToMove = elements
|
||||
.filterNot {
|
||||
it is PsiFile
|
||||
&& CopyFilesOrDirectoriesHandler.checkFileExist(selectedDir, choice, it, it.name, "Move")
|
||||
}
|
||||
.sortedWith( // process Kotlin files first so that light classes are updated before changing references in Java files
|
||||
java.util.Comparator { o1, o2 ->
|
||||
when {
|
||||
o1 is KtElement && o2 !is KtElement -> -1
|
||||
o1 !is KtElement && o2 is KtElement -> 1
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
elementsToMove.forEach {
|
||||
MoveFilesOrDirectoriesUtil.checkMove(it, selectedDir)
|
||||
if (it is KtFile && it.isInKotlinAwareSourceRoot()) {
|
||||
it.updatePackageDirective = updatePackageDirective
|
||||
}
|
||||
}
|
||||
|
||||
if (elementsToMove.isNotEmpty()) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val processor = KotlinAwareMoveFilesOrDirectoriesProcessor(
|
||||
project,
|
||||
elementsToMove as List<KtFile>,
|
||||
selectedDir,
|
||||
searchReferences = moveDialog?.searchReferences ?: true,
|
||||
searchInComments = false,
|
||||
searchInNonJavaFiles = false,
|
||||
moveCallback = moveCallback,
|
||||
prepareSuccessfulCallback = Runnable(::closeDialog)
|
||||
)
|
||||
processor.run()
|
||||
} else {
|
||||
closeDialog()
|
||||
}
|
||||
} catch (e: IncorrectOperationException) {
|
||||
CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), e.message, "refactoring.moveFile", project)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mostly copied from MoveFilesOrDirectoriesUtil.doMove()
|
||||
fun moveFilesOrDirectories(
|
||||
project: Project,
|
||||
elements: Array<PsiElement>,
|
||||
targetElement: PsiElement?,
|
||||
moveCallback: MoveCallback? = 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: KotlinAwareMoveFilesOrDirectoriesDialog?) {
|
||||
invokeMoveFilesOrDirectoriesRefactoring(moveDialog, project, elements, initialTargetDirectory, moveCallback)
|
||||
}
|
||||
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
doRun(null)
|
||||
return
|
||||
}
|
||||
|
||||
with(KotlinAwareMoveFilesOrDirectoriesDialog(project, initialTargetDirectory, ::doRun)) {
|
||||
setData(elements, initialTargetDirectory, "refactoring.moveFile")
|
||||
show()
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,14 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.move
|
||||
|
||||
import com.intellij.ide.util.DirectoryUtil
|
||||
import com.intellij.openapi.options.ConfigurationException
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Comparing
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.refactoring.RefactoringBundle
|
||||
import com.intellij.refactoring.move.moveMembers.MockMoveMembersOptions
|
||||
import com.intellij.refactoring.move.moveMembers.MoveMemberHandler
|
||||
import com.intellij.refactoring.move.moveMembers.MoveMembersProcessor
|
||||
@@ -27,11 +31,15 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||
import org.jetbrains.kotlin.idea.codeInsight.shorten.addDelayedImportRequest
|
||||
import org.jetbrains.kotlin.idea.core.getPackage
|
||||
import org.jetbrains.kotlin.idea.core.util.toPsiDirectory
|
||||
import org.jetbrains.kotlin.idea.imports.importableFqName
|
||||
import org.jetbrains.kotlin.idea.refactoring.fqName.isImported
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference.ShorteningMode
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.idea.util.application.executeCommand
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
@@ -46,6 +54,8 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
|
||||
import org.jetbrains.kotlin.types.expressions.DoubleColonLHS
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
|
||||
sealed class ContainerInfo {
|
||||
@@ -638,4 +648,25 @@ fun collectOuterInstanceReferences(member: KtNamedDeclaration): List<OuterInstan
|
||||
val result = SmartList<OuterInstanceReferenceUsageInfo>()
|
||||
traverseOuterInstanceReferences(member, false) { result += it }
|
||||
return result
|
||||
}
|
||||
|
||||
@Throws(IncorrectOperationException::class)
|
||||
internal fun getOrCreateDirectory(path: String, project: Project): PsiDirectory {
|
||||
|
||||
File(path).toPsiDirectory(project)?.let { return it }
|
||||
|
||||
return project.executeCommand(RefactoringBundle.message("move.title"), null) {
|
||||
runWriteAction {
|
||||
val fixUpSeparators = path.replace(File.separatorChar, '/')
|
||||
DirectoryUtil.mkdirs(PsiManager.getInstance(project), fixUpSeparators)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getTargetPackageFqName(targetContainer: PsiElement): FqName? {
|
||||
if (targetContainer is PsiDirectory) {
|
||||
val targetPackage = targetContainer.getPackage()
|
||||
return if (targetPackage != null) FqName(targetPackage.qualifiedName) else null
|
||||
}
|
||||
return if (targetContainer is KtFile) targetContainer.packageFqName else null
|
||||
}
|
||||
Reference in New Issue
Block a user