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:
Igor Yakovlev
2019-08-20 12:05:35 +03:00
parent d6c5fbace0
commit eca2707dbf
23 changed files with 1117 additions and 868 deletions
@@ -43,6 +43,7 @@ class KotlinRefactoringSettings : PersistentStateComponent<KotlinRefactoringSett
@JvmField var MOVE_PREVIEW_USAGES = true @JvmField var MOVE_PREVIEW_USAGES = true
@JvmField var MOVE_SEARCH_IN_COMMENTS = true @JvmField var MOVE_SEARCH_IN_COMMENTS = true
@JvmField var MOVE_SEARCH_FOR_TEXT = 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_INTERFACE_JAVADOC: Int = 0
@JvmField var EXTRACT_SUPERCLASS_JAVADOC: Int = 0 @JvmField var EXTRACT_SUPERCLASS_JAVADOC: Int = 0
@@ -95,6 +95,7 @@ class ExtractDeclarationFromCurrentFileIntention :
true, true,
true, true,
true, true,
true,
MoveCallback { MoveCallback {
runBlocking { runBlocking {
withTimeoutOrNull(TIMEOUT_FOR_IMPORT_OPTIMIZING_MS) { withTimeoutOrNull(TIMEOUT_FOR_IMPORT_OPTIMIZING_MS) {
@@ -29,7 +29,8 @@ class KotlinAwareMoveFilesOrDirectoriesProcessor @JvmOverloads constructor(
searchInComments: Boolean, searchInComments: Boolean,
searchInNonJavaFiles: Boolean, searchInNonJavaFiles: Boolean,
moveCallback: MoveCallback?, moveCallback: MoveCallback?,
prepareSuccessfulCallback: Runnable = EmptyRunnable.INSTANCE prepareSuccessfulCallback: Runnable = EmptyRunnable.INSTANCE,
private val throwOnConflicts: Boolean = false
) : MoveFilesOrDirectoriesProcessor( ) : MoveFilesOrDirectoriesProcessor(
project, project,
elementsToMove.toTypedArray(), elementsToMove.toTypedArray(),
@@ -58,6 +59,11 @@ class KotlinAwareMoveFilesOrDirectoriesProcessor @JvmOverloads constructor(
return showConflicts(conflicts, usages) 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) { private fun markPsiFiles(mark: PsiFile.() -> Unit) {
fun PsiElement.doMark(mark: PsiFile.() -> Unit) { fun PsiElement.doMark(mark: PsiFile.() -> Unit) {
when (this) { when (this) {
@@ -31,7 +31,6 @@ import com.intellij.refactoring.util.CommonRefactoringUtil
import org.jetbrains.kotlin.asJava.unwrapped import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.idea.core.getPackage import org.jetbrains.kotlin.idea.core.getPackage
import org.jetbrains.kotlin.idea.refactoring.canRefactor 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.KotlinAwareMoveFilesOrDirectoriesDialog
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.KotlinSelectNestedClassRefactoringDialog import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.KotlinSelectNestedClassRefactoringDialog
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.MoveKotlinNestedClassesDialog 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 org.jetbrains.kotlin.psi.psiUtil.isTopLevelInFileOrScript
import java.util.* 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? { private fun getUniqueContainer(elements: Array<out PsiElement>): PsiElement? {
val allTopLevel = elements.all { isTopLevelInFileOrScript(it) } val allTopLevel = elements.all { isTopLevelInFileOrScript(it) }
@@ -72,7 +124,7 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
val container = getUniqueContainer(elements) val container = getUniqueContainer(elements)
if (container == null) { if (container == null) {
CommonRefactoringUtil.showErrorHint( handlerActions.showErrorHint(
project, editor, "All declarations must belong to the same directory or class", MOVE_DECLARATIONS, null project, editor, "All declarations must belong to the same directory or class", MOVE_DECLARATIONS, null
) )
return false return false
@@ -89,20 +141,20 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
// todo: allow moving companion object // todo: allow moving companion object
if (elementsToSearch.any { it is KtObjectDeclaration && it.isCompanion() }) { if (elementsToSearch.any { it is KtObjectDeclaration && it.isCompanion() }) {
val message = RefactoringBundle.getCannotRefactorMessage("Move declaration is not supported for companion objects") 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 return true
} }
if (elementsToSearch.any { !it.canMove() }) { if (elementsToSearch.any { !it.canMove() }) {
val message = val message =
RefactoringBundle.getCannotRefactorMessage("Move declaration is only supported for top-level declarations and nested classes") 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 return true
} }
if (elementsToSearch.any { it is KtEnumEntry }) { if (elementsToSearch.any { it is KtEnumEntry }) {
val message = RefactoringBundle.getCannotRefactorMessage("Move declaration is not supported for enum entries") 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 return true
} }
@@ -113,11 +165,10 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
else -> null else -> null
} }
val initialTargetDirectory = MoveFilesOrDirectoriesUtil.resolveToDirectory(project, initialTargetElement) val initialTargetDirectory = MoveFilesOrDirectoriesUtil.resolveToDirectory(project, initialTargetElement)
val dialog = KotlinAwareMoveFilesOrDirectoriesDialog(project, initialTargetDirectory) {
invokeMoveFilesOrDirectoriesRefactoring(it, project, elements, initialTargetDirectory, callback) handlerActions.invokeKotlinAwareMoveFilesOrDirectoriesRefactoring(
} project, initialTargetDirectory, elements, callback
dialog.setData(elements, initialTargetDirectory, "refactoring.moveFile") )
dialog.show()
return true return true
} }
@@ -128,12 +179,13 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
val targetDirectory = if (targetContainer != null) { val targetDirectory = if (targetContainer != null) {
MoveClassesOrPackagesImpl.getInitialTargetDirectory(targetContainer, elements) MoveClassesOrPackagesImpl.getInitialTargetDirectory(targetContainer, elements)
} else null } else null
val searchInComments = KotlinRefactoringSettings.instance!!.MOVE_SEARCH_IN_COMMENTS val searchInComments = KotlinRefactoringSettings.instance.MOVE_SEARCH_IN_COMMENTS
val searchInText = KotlinRefactoringSettings.instance!!.MOVE_SEARCH_FOR_TEXT val searchInText = KotlinRefactoringSettings.instance.MOVE_SEARCH_FOR_TEXT
val deleteEmptySourceFiles = KotlinRefactoringSettings.instance.MOVE_DELETE_EMPTY_SOURCE_FILES
val targetFile = targetContainer as? KtFile val targetFile = targetContainer as? KtFile
val moveToPackage = targetContainer !is KtFile val moveToPackage = targetContainer !is KtFile
MoveKotlinTopLevelDeclarationsDialog( handlerActions.invokeMoveKotlinTopLevelDeclarationsRefactoring(
project, project,
elementsToSearch, elementsToSearch,
targetPackageName, targetPackageName,
@@ -142,8 +194,9 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
moveToPackage, moveToPackage,
searchInComments, searchInComments,
searchInText, searchInText,
deleteEmptySourceFiles,
callback callback
).show() )
} }
is KtClassOrObject -> { is KtClassOrObject -> {
@@ -152,20 +205,20 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
if (targetContainer !is KtClassOrObject) { if (targetContainer !is KtClassOrObject) {
val message = val message =
RefactoringBundle.getCannotRefactorMessage("Moving multiple nested classes to top-level is not supported") 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 return true
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
MoveKotlinNestedClassesDialog( handlerActions.invokeMoveKotlinNestedClassesRefactoring(
project, project,
elementsToSearch.filterIsInstance<KtClassOrObject>(), elementsToSearch.filterIsInstance<KtClassOrObject>(),
container, container,
targetContainer, targetContainer,
callback callback
).show() )
return true return true
} }
KotlinSelectNestedClassRefactoringDialog.chooseNestedClassRefactoring( handlerActions.invokeKotlinSelectNestedClassChooser(
elementsToSearch.first() as KtClassOrObject, elementsToSearch.first() as KtClassOrObject,
targetContainer targetContainer
) )
@@ -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?)
}
@@ -131,7 +131,8 @@ private object ElementHashingStrategy : TObjectHashingStrategy<PsiElement> {
class MoveKotlinDeclarationsProcessor( class MoveKotlinDeclarationsProcessor(
val descriptor: MoveDeclarationsDescriptor, val descriptor: MoveDeclarationsDescriptor,
val mover: Mover = Mover.Default val mover: Mover = Mover.Default,
private val throwOnConflicts: Boolean = false
) : BaseRefactoringProcessor(descriptor.project) { ) : BaseRefactoringProcessor(descriptor.project) {
companion object { companion object {
private const val REFACTORING_NAME = "Move declarations" private const val REFACTORING_NAME = "Move declarations"
@@ -269,6 +270,11 @@ class MoveKotlinDeclarationsProcessor(
return showConflicts(conflicts, refUsages.get()) 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()) override fun performRefactoring(usages: Array<out UsageInfo>) = doPerformRefactoring(usages.toList())
internal fun doPerformRefactoring(usages: List<UsageInfo>) { internal fun doPerformRefactoring(usages: List<UsageInfo>) {
@@ -143,7 +143,8 @@ private object ElementHashingStrategy : TObjectHashingStrategy<PsiElement> {
class MoveKotlinDeclarationsProcessor( class MoveKotlinDeclarationsProcessor(
val descriptor: MoveDeclarationsDescriptor, 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 { companion object {
private val REFACTORING_NAME = "Move declarations" private val REFACTORING_NAME = "Move declarations"
val REFACTORING_ID = "move.kotlin.declarations" val REFACTORING_ID = "move.kotlin.declarations"
@@ -296,6 +297,11 @@ class MoveKotlinDeclarationsProcessor(
return showConflicts(conflicts, refUsages.get()) 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()) override fun performRefactoring(usages: Array<out UsageInfo>) = doPerformRefactoring(usages.toList())
internal fun doPerformRefactoring(usages: List<UsageInfo>) { internal fun doPerformRefactoring(usages: List<UsageInfo>) {
@@ -37,7 +37,8 @@ class MoveToKotlinFileProcessor @JvmOverloads constructor(
searchInComments: Boolean, searchInComments: Boolean,
searchInNonJavaFiles: Boolean, searchInNonJavaFiles: Boolean,
moveCallback: MoveCallback?, moveCallback: MoveCallback?,
prepareSuccessfulCallback: Runnable = EmptyRunnable.INSTANCE prepareSuccessfulCallback: Runnable = EmptyRunnable.INSTANCE,
private val throwOnConflicts: Boolean = false
) : MoveFilesOrDirectoriesProcessor( ) : MoveFilesOrDirectoriesProcessor(
project, project,
arrayOf(sourceFile), arrayOf(sourceFile),
@@ -59,6 +60,11 @@ class MoveToKotlinFileProcessor @JvmOverloads constructor(
return showConflicts(conflicts, usages) 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 // Assign a temporary name to file-under-move to avoid naming conflict during the refactoring
private fun renameFileTemporarily() { private fun renameFileTemporarily() {
if (targetDirectory.findFile(targetFileName) == null) return if (targetDirectory.findFile(targetFileName) == null) return
@@ -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
}
@@ -5,49 +5,51 @@
package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui
import com.intellij.ide.util.DirectoryUtil
import com.intellij.ide.util.PropertiesComponent import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.IdeActions import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.fileChooser.FileChooserFactory import com.intellij.openapi.fileChooser.FileChooserFactory
import com.intellij.openapi.help.HelpManager
import com.intellij.openapi.keymap.KeymapUtil 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.project.Project
import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.TextComponentAccessor import com.intellij.openapi.ui.TextComponentAccessor
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.refactoring.RefactoringBundle import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.copy.CopyFilesOrDirectoriesDialog 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.refactoring.util.CommonRefactoringUtil
import com.intellij.ui.NonFocusableCheckBox import com.intellij.ui.NonFocusableCheckBox
import com.intellij.ui.RecentsManager import com.intellij.ui.RecentsManager
import com.intellij.ui.TextFieldWithHistoryWithBrowseButton import com.intellij.ui.TextFieldWithHistoryWithBrowseButton
import com.intellij.ui.components.JBLabelDecorator import com.intellij.ui.components.JBLabelDecorator
import com.intellij.util.IncorrectOperationException
import com.intellij.util.ui.FormBuilder import com.intellij.util.ui.FormBuilder
import com.intellij.util.ui.UIUtil import com.intellij.util.ui.UIUtil
import org.jetbrains.kotlin.idea.core.packageMatchesDirectoryOrImplicit import org.jetbrains.kotlin.idea.core.packageMatchesDirectoryOrImplicit
import org.jetbrains.kotlin.idea.core.util.onTextChange import org.jetbrains.kotlin.idea.core.util.onTextChange
import org.jetbrains.kotlin.idea.refactoring.isInKotlinAwareSourceRoot 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.executeCommand
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import java.io.File
import javax.swing.JComponent import javax.swing.JComponent
class KotlinAwareMoveFilesOrDirectoriesDialog( class KotlinAwareMoveFilesOrDirectoriesDialog(
private val project: Project, private val project: Project,
private val initialDirectory: PsiDirectory?, private val initialDirectory: PsiDirectory?,
private val callback: (KotlinAwareMoveFilesOrDirectoriesDialog?) -> Unit private val psiElements: Array<out PsiElement>,
private val callback: MoveCallback?
) : DialogWrapper(project, true) { ) : DialogWrapper(project, true) {
companion object { companion object {
private const val RECENT_KEYS = "MoveFile.RECENT_KEYS" private const val RECENT_KEYS = "MoveFile.RECENT_KEYS"
private const val MOVE_FILES_OPEN_IN_EDITOR = "MoveFile.OpenInEditor" private const val MOVE_FILES_OPEN_IN_EDITOR = "MoveFile.OpenInEditor"
private const val HELP_ID = "refactoring.moveFile"
} }
private val nameLabel = JBLabelDecorator.createJBLabelDecorator().setBold(true) private val nameLabel = JBLabelDecorator.createJBLabelDecorator().setBold(true)
@@ -56,26 +58,19 @@ class KotlinAwareMoveFilesOrDirectoriesDialog(
private val openInEditorCb = NonFocusableCheckBox("Open moved files in editor") private val openInEditorCb = NonFocusableCheckBox("Open moved files in editor")
private val updatePackageDirectiveCb = NonFocusableCheckBox() private val updatePackageDirectiveCb = NonFocusableCheckBox()
private var helpID: String? = null override fun getHelpId() = HELP_ID
var targetDirectory: PsiDirectory? = null
private set
init { init {
title = RefactoringBundle.message("move.title") title = RefactoringBundle.message("move.title")
init() init()
initializeData()
} }
val updatePackageDirective: Boolean
get() = updatePackageDirectiveCb.isSelected
val searchReferences: Boolean
get() = searchReferencesCb.isSelected
override fun createActions() = arrayOf(okAction, cancelAction, helpAction) override fun createActions() = arrayOf(okAction, cancelAction, helpAction)
override fun getPreferredFocusedComponent() = targetDirectoryField.childComponent override fun getPreferredFocusedComponent() = targetDirectoryField.childComponent
override fun createCenterPanel() = null override fun createCenterPanel(): JComponent? = null
override fun createNorthPanel(): JComponent { override fun createNorthPanel(): JComponent {
RecentsManager.getInstance(project).getRecentEntries(RECENT_KEYS)?.let { targetDirectoryField.childComponent.history = it } RecentsManager.getInstance(project).getRecentEntries(RECENT_KEYS)?.let { targetDirectoryField.childComponent.history = it }
@@ -107,7 +102,7 @@ class KotlinAwareMoveFilesOrDirectoriesDialog(
.panel .panel
} }
fun setData(psiElements: Array<out PsiElement>, initialTargetDirectory: PsiDirectory?, helpID: String) { private fun initializeData() {
val psiElement = psiElements.singleOrNull() val psiElement = psiElements.singleOrNull()
if (psiElement != null) { if (psiElement != null) {
val shortenedPath = CopyFilesOrDirectoriesDialog.shortenPath((psiElement as PsiFileSystemItem).virtualFile) 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() validateOKButton()
this.helpID = helpID
with(updatePackageDirectiveCb) { with(updatePackageDirectiveCb) {
val jetFiles = psiElements.filterIsInstance<KtFile>().filter(KtFile::isInKotlinAwareSourceRoot) 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 { private fun isOpenInEditor(): Boolean {
if (ApplicationManager.getApplication().isUnitTestMode) return false if (ApplicationManager.getApplication().isUnitTestMode) return false
return PropertiesComponent.getInstance().getBoolean(MOVE_FILES_OPEN_IN_EDITOR, false) return PropertiesComponent.getInstance().getBoolean(MOVE_FILES_OPEN_IN_EDITOR, false)
@@ -155,41 +147,54 @@ class KotlinAwareMoveFilesOrDirectoriesDialog(
isOKActionEnabled = targetDirectoryField.childComponent.text.isNotEmpty() isOKActionEnabled = targetDirectoryField.childComponent.text.isNotEmpty()
} }
override fun doOKAction() { private val selectedDirectoryName: String
PropertiesComponent.getInstance().setValue(MOVE_FILES_OPEN_IN_EDITOR, openInEditorCb.isSelected, false) get() = targetDirectoryField.childComponent.text.let {
RecentsManager.getInstance(project).registerRecentEntry(RECENT_KEYS, targetDirectoryField.childComponent.text) when {
it.startsWith(".") -> (initialDirectory?.virtualFile?.path ?: "") + "/" + it
else -> it
}
}
if (DumbService.isDumb(project)) { private fun getModel(): Model<KotlinAwareMoveFilesOrDirectoriesProcessor> {
Messages.showMessageDialog(project, "Move refactoring is not available while indexing is in progress", "Indexing", null)
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 return
} }
project.executeCommand(RefactoringBundle.message("move.title"), null) { project.executeCommand(MoveHandler.REFACTORING_NAME) {
runWriteAction { processor.run()
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)
} }
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)
} }
} }
@@ -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
)
}
}
@@ -104,7 +104,12 @@ internal class KotlinSelectNestedClassRefactoringDialog private constructor(
} }
nestedClass is KtEnumEntry -> return nestedClass is KtEnumEntry -> return
else -> { else -> {
val selectionDialog = KotlinSelectNestedClassRefactoringDialog(project, nestedClass, targetContainer) val selectionDialog =
KotlinSelectNestedClassRefactoringDialog(
project,
nestedClass,
targetContainer
)
selectionDialog.show() selectionDialog.show()
if (selectionDialog.exitCode != OK_EXIT_CODE) return if (selectionDialog.exitCode != OK_EXIT_CODE) return
selectionDialog.getNextDialog() ?: return selectionDialog.getNextDialog() ?: return
@@ -15,7 +15,6 @@ import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClass; import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.classMembers.AbstractMemberInfoModel; import com.intellij.refactoring.classMembers.AbstractMemberInfoModel;
import com.intellij.refactoring.classMembers.MemberInfoChange; 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.move.moveDeclarations.*;
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinTypeReferenceEditorComboWithBrowseButton; import org.jetbrains.kotlin.idea.refactoring.ui.KotlinTypeReferenceEditorComboWithBrowseButton;
import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.*;
import javax.swing.*; import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultMutableTreeNode;
import java.awt.*; import java.awt.*;
@@ -82,6 +80,8 @@ public class MoveKotlinNestedClassesDialog extends RefactoringDialog {
initMemberInfo(elementsToMove); initMemberInfo(elementsToMove);
validateButtons(); validateButtons();
myHelpAction.setEnabled(false);
} }
private void initClassChooser(@NotNull KtClassOrObject initialTargetClass) { private void initClassChooser(@NotNull KtClassOrObject initialTargetClass) {
@@ -217,40 +217,29 @@ public class MoveKotlinNestedClassesDialog extends RefactoringDialog {
return "#" + getClass().getName(); return "#" + getClass().getName();
} }
@Override private Model<MoveKotlinDeclarationsProcessor> getModel() {
protected void canRun() throws ConfigurationException { return new MoveKotlinNestedClassesModel(
if (targetClass == null) throw new ConfigurationException("No destination class specified"); myProject,
openInEditorCheckBox.isSelected(),
if (!(targetClass instanceof KtClassOrObject)) throw new ConfigurationException("Destination class must be a Kotlin class"); getSelectedElementsToMove(),
originalClass,
if (originalClass == targetClass) { targetClass,
throw new ConfigurationException(RefactoringBundle.message("source.and.destination.classes.should.be.different")); moveCallback);
}
for (KtClassOrObject classOrObject : getSelectedElementsToMove()) {
if (PsiTreeUtil.isAncestor(classOrObject, targetClass, false)) {
throw new ConfigurationException("Cannot move nested class " + classOrObject.getName() + " to itself");
}
}
} }
@Override @Override
protected void doAction() { protected void doAction() {
List<KtClassOrObject> elementsToMove = getSelectedElementsToMove();
KotlinMoveTarget target = new KotlinMoveTargetForExistingElement((KtClassOrObject) targetClass); MoveKotlinDeclarationsProcessor processor;
MoveDeclarationsDelegate.NestedClass delegate = new MoveDeclarationsDelegate.NestedClass(); try {
MoveDeclarationsDescriptor descriptor = new MoveDeclarationsDescriptor( processor = getModel().computeModelResult();
myProject, }
MoveKotlinDeclarationsProcessorKt.MoveSource(elementsToMove), catch (ConfigurationException e) {
target, setErrorText(e.getMessage());
delegate, return;
false, }
false,
false, invokeRefactoring(processor);
moveCallback,
openInEditorCheckBox.isSelected()
);
invokeRefactoring(new MoveKotlinDeclarationsProcessor(descriptor, Mover.Default.INSTANCE));
} }
@Override @Override
@@ -258,7 +247,5 @@ public class MoveKotlinNestedClassesDialog extends RefactoringDialog {
return targetClassChooser.getChildComponent(); return targetClassChooser.getChildComponent();
} }
private static class MemberInfoModelImpl extends AbstractMemberInfoModel<KtNamedDeclaration, KotlinMemberInfo> { private static class MemberInfoModelImpl extends AbstractMemberInfoModel<KtNamedDeclaration, KotlinMemberInfo> { }
}
} }
@@ -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)
}
}
@@ -5,53 +5,35 @@
package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui; 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.options.ConfigurationException;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.psi.PsiDirectory;
import com.intellij.openapi.util.Comparing; import com.intellij.psi.PsiElement;
import com.intellij.openapi.util.NullableComputable;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.refactoring.HelpID; import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.PackageWrapper;
import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.move.MoveDialogBase; 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.NameSuggestionsField;
import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo; 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.ui.EditorTextField;
import com.intellij.util.ArrayUtil; import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import kotlin.jvm.functions.Function1; import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.ClassDescriptor; 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.caches.resolve.ResolutionUtils;
import org.jetbrains.kotlin.idea.core.CollectingNameValidator; import org.jetbrains.kotlin.idea.core.CollectingNameValidator;
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester; import org.jetbrains.kotlin.idea.core.KotlinNameSuggester;
import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator; 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.KotlinRefactoringSettings;
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtilKt;
import org.jetbrains.kotlin.idea.refactoring.move.MoveUtilsKt; import org.jetbrains.kotlin.idea.refactoring.move.MoveUtilsKt;
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*; import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveKotlinDeclarationsProcessor;
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.KtClass;
import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.KtClassBody;
import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt; import org.jetbrains.kotlin.psi.KtClassOrObject;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode; import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;
import org.jetbrains.kotlin.types.KotlinType; import org.jetbrains.kotlin.types.KotlinType;
import static org.jetbrains.kotlin.idea.roots.ProjectRootUtilsKt.getSuitableDestinationSourceRoots;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@@ -96,16 +78,6 @@ public class MoveKotlinNestedClassesToUpperLevelDialog extends MoveDialogBase {
openInEditorPanel.add(initOpenInEditorCb(), BorderLayout.EAST); 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() { private void createUIComponents() {
parameterField = new NameSuggestionsField(project); parameterField = new NameSuggestionsField(project);
packageNameField = new PackageNameReferenceEditorCombo("", project, RECENTS_KEY, packageNameField = new PackageNameReferenceEditorCombo("", project, RECENTS_KEY,
@@ -127,14 +99,6 @@ public class MoveKotlinNestedClassesToUpperLevelDialog extends MoveDialogBase {
return "Open moved member in editor"; return "Open moved member in editor";
} }
public boolean isSearchInComments() {
return searchInCommentsCheckBox.isSelected();
}
public boolean isSearchInNonJavaFiles() {
return searchForTextOccurrencesCheckBox.isSelected();
}
public String getClassName() { public String getClassName() {
return classNameField.getText().trim(); return classNameField.getText().trim();
} }
@@ -150,7 +114,7 @@ public class MoveKotlinNestedClassesToUpperLevelDialog extends MoveDialogBase {
@Nullable @Nullable
private FqName getTargetPackageFqName() { private FqName getTargetPackageFqName() {
return getTargetPackageFqName(targetContainer); return MoveUtilsKt.getTargetPackageFqName(targetContainer);
} }
@NotNull @NotNull
@@ -236,127 +200,30 @@ public class MoveKotlinNestedClassesToUpperLevelDialog extends MoveDialogBase {
return null; return null;
} }
@Nullable private Model<MoveKotlinDeclarationsProcessor> getModel() {
private PsiElement getTargetContainer() { return new MoveKotlinNestedClassesToUpperLevelModel(
if (targetContainer instanceof PsiDirectory) { project,
PsiDirectory psiDirectory = (PsiDirectory) targetContainer; innerClass,
FqName oldPackageFqName = getTargetPackageFqName(); targetContainer,
String targetName = packageNameField.getText(); getParameterName(),
if (!Comparing.equal(oldPackageFqName != null ? oldPackageFqName.asString() : null, targetName)) { getClassName(),
ProjectRootManager projectRootManager = ProjectRootManager.getInstance(project); passOuterClassCheckBox.isSelected(),
searchInCommentsCheckBox.isSelected(),
List<VirtualFile> contentSourceRoots = getSuitableDestinationSourceRoots(project); searchForTextOccurrencesCheckBox.isSelected(),
PackageWrapper newPackage = new PackageWrapper(PsiManager.getInstance(project), targetName); packageNameField.getText(),
VirtualFile targetSourceRoot; isOpenInEditor());
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;
} }
@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 @Override
protected void doAction() { protected void doAction() {
PsiElement target;
MoveKotlinDeclarationsProcessor processor;
try { try {
target = getTargetContainerWithValidation(); processor = getModel().computeModelResult();
if (target == null) return;
} }
catch (ConfigurationException e) { catch (ConfigurationException e) {
CommonRefactoringUtil.showErrorMessage(MoveInnerImpl.REFACTORING_NAME, e.getMessage(), HelpID.MOVE_INNER_UPPER, project); setErrorText(e.getMessage());
return; 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_FOR_TEXT = searchForTextOccurrencesCheckBox.isSelected();
settings.MOVE_TO_UPPER_LEVEL_SEARCH_IN_COMMENTS = searchInCommentsCheckBox.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(); saveOpenInEditorOption();
invokeRefactoring(new MoveKotlinDeclarationsProcessor(moveDescriptor, Mover.Default.INSTANCE)); invokeRefactoring(processor);
} }
} }
@@ -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)
}
}
@@ -116,7 +116,7 @@
<text value="&amp;Update package directive"/> <text value="&amp;Update package directive"/>
</properties> </properties>
</component> </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"/> <margin top="0" left="0" bottom="0" right="0"/>
<constraints> <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"/> <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 &amp;references"/> <text value="Search &amp;references"/>
</properties> </properties>
</component> </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="&amp;Delete empty source files"/>
</properties>
</component>
</children> </children>
</grid> </grid>
</children> </children>
@@ -6,31 +6,20 @@
package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui; package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui;
import com.intellij.ide.util.DirectoryChooser; 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.options.ConfigurationException;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Pass; import com.intellij.openapi.util.Pass;
import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDirectory;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile;
import com.intellij.psi.*; import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.psi.impl.source.PsiFileImpl; import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.*;
import com.intellij.refactoring.classMembers.AbstractMemberInfoModel; import com.intellij.refactoring.classMembers.AbstractMemberInfoModel;
import com.intellij.refactoring.classMembers.MemberInfoBase; import com.intellij.refactoring.classMembers.MemberInfoBase;
import com.intellij.refactoring.classMembers.MemberInfoChange; import com.intellij.refactoring.classMembers.MemberInfoChange;
import com.intellij.refactoring.classMembers.MemberInfoChangeListener; import com.intellij.refactoring.classMembers.MemberInfoChangeListener;
import com.intellij.refactoring.move.MoveCallback; import com.intellij.refactoring.move.MoveCallback;
import com.intellij.refactoring.move.MoveHandler; 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.PackageNameReferenceEditorCombo;
import com.intellij.refactoring.ui.RefactoringDialog; import com.intellij.refactoring.ui.RefactoringDialog;
import com.intellij.refactoring.util.CommonRefactoringUtil; import com.intellij.refactoring.util.CommonRefactoringUtil;
@@ -42,35 +31,29 @@ import com.intellij.util.ui.UIUtil;
import kotlin.collections.CollectionsKt; import kotlin.collections.CollectionsKt;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.KotlinFileType;
import org.jetbrains.kotlin.idea.core.PackageUtilsKt; import org.jetbrains.kotlin.idea.core.PackageUtilsKt;
import org.jetbrains.kotlin.idea.core.util.PhysicalFileSystemUtilsKt; 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.KotlinRefactoringSettings;
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtilKt;
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo; import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo;
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionPanel; import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionPanel;
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionTable; import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionTable;
import org.jetbrains.kotlin.idea.refactoring.move.MoveUtilsKt; 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.KotlinDestinationFolderComboBox;
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinFileChooserDialog; 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.KtFile;
import org.jetbrains.kotlin.psi.KtNamedDeclaration; import org.jetbrains.kotlin.psi.KtNamedDeclaration;
import org.jetbrains.kotlin.psi.KtPureElement; import org.jetbrains.kotlin.psi.KtPureElement;
import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt; import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt;
import static org.jetbrains.kotlin.idea.roots.ProjectRootUtilsKt.getSuitableDestinationSourceRoots;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.io.File; import java.io.File;
import java.nio.file.Path; import java.util.Collection;
import java.util.List; 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 { public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
private static final String RECENTS_KEY = "MoveKotlinTopLevelDeclarationsDialog.RECENTS_KEY"; private static final String RECENTS_KEY = "MoveKotlinTopLevelDeclarationsDialog.RECENTS_KEY";
@@ -89,9 +72,42 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
private JTextField tfFileNameInPackage; private JTextField tfFileNameInPackage;
private JCheckBox cbSpecifyFileNameInPackage; private JCheckBox cbSpecifyFileNameInPackage;
private JCheckBox cbUpdatePackageDirective; private JCheckBox cbUpdatePackageDirective;
private JCheckBox cbDeleteEmptySourceFiles;
private JCheckBox cbSearchReferences; private JCheckBox cbSearchReferences;
private KotlinMemberSelectionTable memberTable; 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( public MoveKotlinTopLevelDeclarationsDialog(
@NotNull Project project, @NotNull Project project,
@NotNull Set<KtNamedDeclaration> elementsToMove, @NotNull Set<KtNamedDeclaration> elementsToMove,
@@ -100,7 +116,8 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
@Nullable KtFile targetFile, @Nullable KtFile targetFile,
boolean moveToPackage, boolean moveToPackage,
boolean searchInComments, boolean searchInComments,
boolean searchForTextOccurences, boolean searchForTextOccurrences,
boolean deleteEmptySourceFiles,
@Nullable MoveCallback moveCallback @Nullable MoveCallback moveCallback
) { ) {
super(project, true); super(project, true);
@@ -114,7 +131,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
setTitle(MoveHandler.REFACTORING_NAME); setTitle(MoveHandler.REFACTORING_NAME);
initSearchOptions(searchInComments, searchForTextOccurences); initSearchOptions(searchInComments, searchForTextOccurrences, deleteEmptySourceFiles);
initPackageChooser(targetPackageName, targetDirectory, sourceFiles); 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) { private static List<KtNamedDeclaration> getAllDeclarations(Collection<KtFile> sourceFiles) {
return CollectionsKt.filterIsInstance( return CollectionsKt.filterIsInstance(
CollectionsKt.flatMap( CollectionsKt.flatMap(
@@ -165,30 +170,6 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
return true; 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( private void initMemberInfo(
@NotNull Set<KtNamedDeclaration> elementsToMove, @NotNull Set<KtNamedDeclaration> elementsToMove,
@NotNull List<KtFile> sourceFiles @NotNull List<KtFile> sourceFiles
@@ -207,34 +188,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
memberInfoModel.memberInfoChanged(new MemberInfoChange<>(memberInfos)); memberInfoModel.memberInfoChanged(new MemberInfoChange<>(memberInfos));
selectionPanel.getTable().setMemberInfoModel(memberInfoModel); selectionPanel.getTable().setMemberInfoModel(memberInfoModel);
selectionPanel.getTable().addMemberInfoChangeListener(memberInfoModel); selectionPanel.getTable().addMemberInfoChangeListener(memberInfoModel);
selectionPanel.getTable().addMemberInfoChangeListener( selectionPanel.getTable().addMemberInfoChangeListener(new MemberSelectionerInfoChangeListener(memberInfos));
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();
}
}
}
);
memberInfoPanel.add(selectionPanel, BorderLayout.CENTER); memberInfoPanel.add(selectionPanel, BorderLayout.CENTER);
} }
@@ -243,8 +197,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
} }
private void updateFileNameInPackageField() { private void updateFileNameInPackageField() {
boolean movingSingleFileToPackage = isMoveToPackage() boolean movingSingleFileToPackage = rbMoveToPackage.isSelected() && getSourceFiles(getSelectedElementsToMove()).size() == 1;
&& getSourceFiles(getSelectedElementsToMove()).size() == 1;
cbSpecifyFileNameInPackage.setEnabled(movingSingleFileToPackage); cbSpecifyFileNameInPackage.setEnabled(movingSingleFileToPackage);
tfFileNameInPackage.setEnabled(movingSingleFileToPackage && cbSpecifyFileNameInPackage.isSelected()); tfFileNameInPackage.setEnabled(movingSingleFileToPackage && cbSpecifyFileNameInPackage.isSelected());
} }
@@ -275,9 +228,10 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
cbUpdatePackageDirective.setSelected(arePackagesAndDirectoryMatched(sourceFiles)); cbUpdatePackageDirective.setSelected(arePackagesAndDirectoryMatched(sourceFiles));
} }
private void initSearchOptions(boolean searchInComments, boolean searchForTextOccurences) { private void initSearchOptions(boolean searchInComments, boolean searchForTextOccurences, boolean deleteEmptySourceFiles) {
cbSearchInComments.setSelected(searchInComments); cbSearchInComments.setSelected(searchInComments);
cbSearchTextOccurrences.setSelected(searchForTextOccurences); cbSearchTextOccurrences.setSelected(searchForTextOccurences);
cbDeleteEmptySourceFiles.setSelected(deleteEmptySourceFiles);
} }
private void initMoveToButtons(boolean moveToPackage) { private void initMoveToButtons(boolean moveToPackage) {
@@ -309,13 +263,14 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
@NotNull List<KtFile> sourceFiles @NotNull List<KtFile> sourceFiles
) { ) {
PsiDirectory sourceDir = sourceFiles.get(0).getParent(); 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( fileChooser.addActionListener(e -> {
e -> {
KotlinFileChooserDialog dialog = new KotlinFileChooserDialog("Choose Containing File", myProject); 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); PsiFile targetPsiFile = PhysicalFileSystemUtilsKt.toPsiFile(targetFile1, myProject);
if (targetPsiFile instanceof KtFile) { if (targetPsiFile instanceof KtFile) {
dialog.select((KtFile) targetPsiFile); dialog.select((KtFile) targetPsiFile);
@@ -336,12 +291,9 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
} }
); );
String initialTargetPath = String initialTargetPath = targetFile != null
targetFile != null
? targetFile.getVirtualFile().getPath() ? targetFile.getVirtualFile().getPath()
: sourceFiles.get(0).getVirtualFile().getParent().getPath() + : sourceFiles.get(0).getVirtualFile().getParent().getPath() + "/" + MoveUtilsKt.guessNewFileName(elementsToMove);
"/" +
MoveUtilsKt.guessNewFileName(elementsToMove);
fileChooser.setText(initialTargetPath); fileChooser.setText(initialTargetPath);
} }
@@ -357,28 +309,23 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
} }
private ReferenceEditorComboWithBrowseButton createPackageChooser() { private ReferenceEditorComboWithBrowseButton createPackageChooser() {
ReferenceEditorComboWithBrowseButton packageChooser = return new PackageNameReferenceEditorCombo(
new PackageNameReferenceEditorCombo("", myProject, RECENTS_KEY, RefactoringBundle.message("choose.destination.package")); "",
Document document = packageChooser.getChildComponent().getDocument(); myProject,
document.addDocumentListener(new DocumentListener() { RECENTS_KEY,
@Override RefactoringBundle.message("choose.destination.package")
public void documentChanged(@NotNull DocumentEvent e) { );
validateButtons();
}
});
return packageChooser;
} }
private void updateControls() { private void updateControls() {
boolean moveToPackage = isMoveToPackage(); boolean moveToPackage = rbMoveToPackage.isSelected();
classPackageChooser.setEnabled(moveToPackage); classPackageChooser.setEnabled(moveToPackage);
updateFileNameInPackageField(); updateFileNameInPackageField();
fileChooser.setEnabled(!moveToPackage); fileChooser.setEnabled(!moveToPackage);
updatePackageDirectiveCheckBox(); updatePackageDirectiveCheckBox();
UIUtil.setEnabled(targetPanel, moveToPackage && hasAnySourceRoots(), true); UIUtil.setEnabled(targetPanel, moveToPackage && hasAnySourceRoots(), true);
updateSuggestedFileName(); updateSuggestedFileName();
validateButtons(); myHelpAction.setEnabled(false);
} }
private boolean isFullFileMove() { private boolean isFullFileMove() {
@@ -393,7 +340,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
} }
private void updatePackageDirectiveCheckBox() { private void updatePackageDirectiveCheckBox() {
cbUpdatePackageDirective.setEnabled(isMoveToPackage() && isFullFileMove()); cbUpdatePackageDirective.setEnabled(rbMoveToPackage.isSelected() && isFullFileMove());
} }
private boolean hasAnySourceRoots() { private boolean hasAnySourceRoots() {
@@ -402,206 +349,12 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
private void saveRefactoringSettings() { private void saveRefactoringSettings() {
KotlinRefactoringSettings refactoringSettings = KotlinRefactoringSettings.getInstance(); KotlinRefactoringSettings refactoringSettings = KotlinRefactoringSettings.getInstance();
refactoringSettings.MOVE_SEARCH_IN_COMMENTS = isSearchInComments(); refactoringSettings.MOVE_SEARCH_IN_COMMENTS = cbSearchInComments.isSelected();
refactoringSettings.MOVE_SEARCH_FOR_TEXT = isSearchInNonJavaFiles(); refactoringSettings.MOVE_SEARCH_FOR_TEXT = cbSearchTextOccurrences.isSelected();
refactoringSettings.MOVE_DELETE_EMPTY_SOURCE_FILES = cbDeleteEmptySourceFiles.isSelected();
refactoringSettings.MOVE_PREVIEW_USAGES = isPreviewUsages(); refactoringSettings.MOVE_PREVIEW_USAGES = isPreviewUsages();
}
@Nullable RecentsManager.getInstance(myProject).registerRecentEntry(RECENTS_KEY, getTargetPackage());
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;
} }
private List<KtNamedDeclaration> getSelectedElementsToMove() { private List<KtNamedDeclaration> getSelectedElementsToMove() {
@@ -621,136 +374,66 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
return "#" + getClass().getName(); return "#" + getClass().getName();
} }
protected final String getTargetPackage() { private String getTargetPackage() {
return classPackageChooser.getText().trim(); return classPackageChooser.getText().trim();
} }
protected final String getTargetFilePath() { private List<KtNamedDeclaration> getSelectedElementsToMoveChecked() throws ConfigurationException {
return fileChooser.getText(); List<KtNamedDeclaration> elementsToMove = getSelectedElementsToMove();
if (elementsToMove.isEmpty()) {
throw new ConfigurationException("No elements to move are selected");
}
return elementsToMove;
} }
@Override private Model<BaseRefactoringProcessor> getModel() throws ConfigurationException {
protected void canRun() throws ConfigurationException {
String message = verifyBeforeRun(); DirectoryChooser.ItemWrapper selectedItem = (DirectoryChooser.ItemWrapper) destinationFolderCB.getComboBox().getSelectedItem();
if (message != null) { PsiDirectory selectedPsiDirectory = selectedItem != null ? selectedItem.getDirectory() : initialTargetDirectory;
throw new ConfigurationException(message);
} 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 @Override
protected void doAction() { protected void doAction() {
KotlinMoveTarget target = selectMoveTarget();
if (target == null) return; BaseRefactoringProcessor processor;
try {
processor = getModel().computeModelResult();
}
catch (ConfigurationException e) {
setErrorText(e.getMessage());
return;
}
saveRefactoringSettings(); 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 { try {
boolean deleteSourceFile = false; invokeRefactoring(processor);
} catch (IncorrectOperationException e) {
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) {
CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), e.getMessage(), null, myProject); 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 @Override
public JComponent getPreferredFocusedComponent() { public JComponent getPreferredFocusedComponent() {
return classPackageChooser.getChildComponent(); return classPackageChooser.getChildComponent();
} }
private static class MemberInfoModelImpl extends AbstractMemberInfoModel<KtNamedDeclaration, KotlinMemberInfo> { private static class MemberInfoModelImpl extends AbstractMemberInfoModel<KtNamedDeclaration, KotlinMemberInfo> { }
}
} }
@@ -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)
}
}
@@ -7,13 +7,18 @@ package org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.LangDataKeys import com.intellij.openapi.actionSystem.LangDataKeys
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.refactoring.move.MoveCallback 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.MoveFilesOrDirectoriesHandler
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade 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.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile 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?) { override fun doMove(project: Project, elements: Array<out PsiElement>, targetContainer: PsiElement?, callback: MoveCallback?) {
if (!(targetContainer == null || targetContainer is PsiDirectory || targetContainer is PsiDirectoryContainer)) return if (!(targetContainer == null || targetContainer is PsiDirectory || targetContainer is PsiDirectoryContainer)) return
moveFilesOrDirectories( val adjustedElementsToMove = adjustForMove(project, elements, targetContainer)?.toList() ?: return
project,
adjustForMove(project, elements, targetContainer) ?: return, val targetDirectory = MoveFilesOrDirectoriesUtil.resolveToDirectory(project, targetContainer)
targetContainer, if (targetContainer != null && targetDirectory == null) return
callback?.let { MoveCallback { it.refactoringCompleted() } }
) 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( 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 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.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Comparing import com.intellij.openapi.util.Comparing
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.move.moveMembers.MockMoveMembersOptions import com.intellij.refactoring.move.moveMembers.MockMoveMembersOptions
import com.intellij.refactoring.move.moveMembers.MoveMemberHandler import com.intellij.refactoring.move.moveMembers.MoveMemberHandler
import com.intellij.refactoring.move.moveMembers.MoveMembersProcessor 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.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.codeInsight.shorten.addDelayedImportRequest 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.imports.importableFqName
import org.jetbrains.kotlin.idea.refactoring.fqName.isImported import org.jetbrains.kotlin.idea.refactoring.fqName.isImported
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference.ShorteningMode import org.jetbrains.kotlin.idea.references.KtSimpleNameReference.ShorteningMode
import org.jetbrains.kotlin.idea.references.mainReference 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.load.java.descriptors.JavaCallableMemberDescriptor
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.* 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.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.types.expressions.DoubleColonLHS import org.jetbrains.kotlin.types.expressions.DoubleColonLHS
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
import java.io.File
import java.nio.file.Path
import java.util.* import java.util.*
sealed class ContainerInfo { sealed class ContainerInfo {
@@ -638,4 +648,25 @@ fun collectOuterInstanceReferences(member: KtNamedDeclaration): List<OuterInstan
val result = SmartList<OuterInstanceReferenceUsageInfo>() val result = SmartList<OuterInstanceReferenceUsageInfo>()
traverseOuterInstanceReferences(member, false) { result += it } traverseOuterInstanceReferences(member, false) { result += it }
return result 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
} }