diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/KotlinAwareMoveFilesOrDirectoriesDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/KotlinAwareMoveFilesOrDirectoriesDialog.kt new file mode 100644 index 00000000000..54efa052237 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/KotlinAwareMoveFilesOrDirectoriesDialog.kt @@ -0,0 +1,202 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.ui + +import com.intellij.ide.util.DirectoryUtil +import com.intellij.ide.util.PropertiesComponent +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.IdeActions +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory +import com.intellij.openapi.fileChooser.FileChooserFactory +import com.intellij.openapi.help.HelpManager +import com.intellij.openapi.keymap.KeymapUtil +import com.intellij.openapi.project.DumbModePermission +import com.intellij.openapi.project.DumbService +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.ui.TextComponentAccessor +import com.intellij.openapi.util.Disposer +import com.intellij.psi.* +import com.intellij.refactoring.RefactoringBundle +import com.intellij.refactoring.copy.CopyFilesOrDirectoriesDialog +import com.intellij.refactoring.util.CommonRefactoringUtil +import com.intellij.ui.DocumentAdapter +import com.intellij.ui.NonFocusableCheckBox +import com.intellij.ui.RecentsManager +import com.intellij.ui.TextFieldWithHistoryWithBrowseButton +import com.intellij.ui.components.JBLabelDecorator +import com.intellij.util.IncorrectOperationException +import com.intellij.util.ui.FormBuilder +import com.intellij.util.ui.UIUtil +import org.jetbrains.kotlin.idea.core.packageMatchesDirectory +import org.jetbrains.kotlin.idea.core.refactoring.isInJavaSourceRoot +import org.jetbrains.kotlin.idea.util.application.executeCommand +import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.psi.KtFile +import java.io.File +import javax.swing.JComponent +import javax.swing.event.DocumentEvent + +public class KotlinAwareMoveFilesOrDirectoriesDialog( + private val project: Project, + private val callback: (KotlinAwareMoveFilesOrDirectoriesDialog?) -> Unit +) : DialogWrapper(project, true) { + companion object { + private val RECENT_KEYS = "MoveFile.RECENT_KEYS" + private val MOVE_FILES_OPEN_IN_EDITOR = "MoveFile.OpenInEditor" + } + + private val nameLabel = JBLabelDecorator.createJBLabelDecorator().setBold(true) + private val targetDirectoryField = TextFieldWithHistoryWithBrowseButton() + private val openInEditorCb = NonFocusableCheckBox("Open moved files in editor") + private val updatePackageDirectiveCb = NonFocusableCheckBox() + + private var helpID: String? = null + var targetDirectory: PsiDirectory? = null + private set + + init { + title = RefactoringBundle.message("move.title") + init() + } + + public val updatePackageDirective: Boolean + get() = updatePackageDirectiveCb.isSelected + + override fun createActions() = arrayOf(okAction, cancelAction, helpAction) + + override fun getPreferredFocusedComponent() = targetDirectoryField.childComponent + + override fun createCenterPanel() = null + + override fun createNorthPanel(): JComponent { + RecentsManager.getInstance(project).getRecentEntries(RECENT_KEYS)?.let { targetDirectoryField.childComponent.history = it } + + val descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor() + targetDirectoryField.addBrowseFolderListener(RefactoringBundle.message("select.target.directory"), + RefactoringBundle.message("the.file.will.be.moved.to.this.directory"), + project, + descriptor, + TextComponentAccessor.TEXT_FIELD_WITH_HISTORY_WHOLE_TEXT) + val textField = targetDirectoryField.childComponent.textEditor + FileChooserFactory.getInstance().installFileCompletion(textField, descriptor, true, disposable) + textField.document.addDocumentListener( + object : DocumentAdapter() { + override fun textChanged(e: DocumentEvent) { + validateOKButton() + } + } + ) + targetDirectoryField.setTextFieldPreferredWidth(CopyFilesOrDirectoriesDialog.MAX_PATH_LENGTH) + Disposer.register(disposable, targetDirectoryField) + + openInEditorCb.isSelected = isOpenInEditor() + + val shortcutText = KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction(IdeActions.ACTION_CODE_COMPLETION)) + return FormBuilder.createFormBuilder() + .addComponent(nameLabel) + .addLabeledComponent(RefactoringBundle.message("move.files.to.directory.label"), targetDirectoryField, UIUtil.LARGE_VGAP) + .addTooltip(RefactoringBundle.message("path.completion.shortcut", shortcutText)) + .addComponentToRightColumn(openInEditorCb, UIUtil.LARGE_VGAP) + .addComponentToRightColumn(updatePackageDirectiveCb, UIUtil.LARGE_VGAP) + .panel + } + + fun setData(psiElements: Array, initialTargetDirectory: PsiDirectory?, helpID: String) { + val psiElement = psiElements.singleOrNull() + if (psiElement != null) { + val shortenedPath = CopyFilesOrDirectoriesDialog.shortenPath((psiElement as PsiFileSystemItem).virtualFile) + nameLabel.text = when (psiElement) { + is PsiFile -> RefactoringBundle.message("move.file.0", shortenedPath) + else -> RefactoringBundle.message("move.directory.0", shortenedPath) + } + } + else { + val isFile = psiElements.all { it is PsiFile } + val isDirectory = psiElements.all { it is PsiDirectory } + nameLabel.text = when { + isFile -> RefactoringBundle.message("move.specified.files") + isDirectory -> RefactoringBundle.message("move.specified.directories") + else -> RefactoringBundle.message("move.specified.elements") + } + } + + targetDirectoryField.childComponent.text = initialTargetDirectory?.virtualFile?.presentableUrl ?: "" + + validateOKButton() + this.helpID = helpID + + with (updatePackageDirectiveCb) { + val jetFiles = psiElements.filterIsInstance().filter { it.isInJavaSourceRoot() } + if (jetFiles.isEmpty()) { + parent.remove(updatePackageDirectiveCb) + return + } + + val singleFile = jetFiles.singleOrNull() + isSelected = singleFile == null || singleFile.packageMatchesDirectory() + text = "Update package directive (Kotlin files)" + } + } + + override fun doHelpAction() = HelpManager.getInstance().invokeHelp(helpID) + + fun isOpenInEditor(): Boolean { + if (ApplicationManager.getApplication().isUnitTestMode) return false + return PropertiesComponent.getInstance().getBoolean(MOVE_FILES_OPEN_IN_EDITOR, true) + } + + private fun validateOKButton() { + isOKActionEnabled = targetDirectoryField.childComponent.text.length > 0 + } + + override fun doOKAction() { + PropertiesComponent.getInstance().setValue(MOVE_FILES_OPEN_IN_EDITOR, openInEditorCb.isSelected, true) + RecentsManager.getInstance(project).registerRecentEntry(RECENT_KEYS, targetDirectoryField.childComponent.text) + + if (DumbService.isDumb(project)) { + Messages.showMessageDialog(project, "Move refactoring is not available while indexing is in progress", "Indexing", null) + return + } + + project.executeCommand(RefactoringBundle.message("move.title"), null) { + DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_MODAL) { + runWriteAction { + val directoryName = targetDirectoryField.childComponent.text.replace(File.separatorChar, '/') + 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@allowStartingDumbModeInside + } + + callback(this@KotlinAwareMoveFilesOrDirectoriesDialog) + } + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/MoveFilesOrDirectoriesDialogWithKotlinOptions.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/MoveFilesOrDirectoriesDialogWithKotlinOptions.kt deleted file mode 100644 index a59be46022b..00000000000 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveTopLevelDeclarations/ui/MoveFilesOrDirectoriesDialogWithKotlinOptions.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2010-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.ui - -import com.intellij.openapi.project.Project -import com.intellij.psi.PsiDirectory -import com.intellij.psi.PsiElement -import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesDialog -import com.intellij.ui.NonFocusableCheckBox -import com.intellij.util.ui.UIUtil -import org.jetbrains.kotlin.idea.core.refactoring.isInJavaSourceRoot -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.idea.core.packageMatchesDirectory -import java.awt.GridBagConstraints -import java.awt.Insets -import javax.swing.JCheckBox -import javax.swing.JComponent -import javax.swing.JLabel -import kotlin.properties.Delegates - -public class MoveFilesOrDirectoriesDialogWithKotlinOptions( - project: Project, - callback: (MoveFilesOrDirectoriesDialog?) -> Unit): MoveFilesOrDirectoriesDialog(project, callback) { - private var cbUpdatePackageDirective: JCheckBox? = null - - public val updatePackageDirective: Boolean - get() = cbUpdatePackageDirective!!.isSelected() - - override fun createNorthPanel(): JComponent { - val panel = super.createNorthPanel() - - val gbc = GridBagConstraints() - - gbc.gridwidth = 1 - gbc.gridx = 0 - gbc.gridy = GridBagConstraints.RELATIVE - gbc.weightx = 0.0 - gbc.weighty = 0.0 - gbc.fill = GridBagConstraints.NONE - gbc.anchor = GridBagConstraints.WEST - gbc.insets = Insets(UIUtil.LARGE_VGAP, 0, 0, UIUtil.DEFAULT_HGAP) - panel!!.add(JLabel(), gbc) - - cbUpdatePackageDirective = NonFocusableCheckBox() - gbc.gridx = 1 - gbc.weightx = 1.0 - gbc.weighty = 0.0 - gbc.fill = GridBagConstraints.HORIZONTAL - gbc.anchor = GridBagConstraints.WEST - gbc.insets = Insets(UIUtil.LARGE_VGAP, 0, 0, 0) - panel.add(cbUpdatePackageDirective, gbc) - - return panel - } - - override fun setData(psiElements: Array, initialTargetDirectory: PsiDirectory?, helpID: String) { - super.setData(psiElements, initialTargetDirectory, helpID) - - with (cbUpdatePackageDirective!!) { - val jetFiles = psiElements.filterIsInstance().filter { it.isInJavaSourceRoot() } - if (jetFiles.isEmpty()) { - getParent().remove(cbUpdatePackageDirective) - return - } - - val singleFile = jetFiles.singleOrNull() - setSelected(singleFile == null || singleFile.packageMatchesDirectory()) - setText("Update package directive (Kotlin files)") - } - } -} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt index 95e89312491..15a1f2a1416 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt @@ -27,7 +27,6 @@ import com.intellij.refactoring.RefactoringBundle import com.intellij.refactoring.RefactoringSettings import com.intellij.refactoring.copy.CopyFilesOrDirectoriesHandler import com.intellij.refactoring.move.MoveHandler -import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesDialog import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesProcessor import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil import com.intellij.refactoring.move.moveMembers.MockMoveMembersOptions @@ -49,7 +48,7 @@ import org.jetbrains.kotlin.idea.codeInsight.KotlinFileReferencesResolver import org.jetbrains.kotlin.idea.core.refactoring.isInJavaSourceRoot import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.idea.refactoring.fqName.isImported -import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.ui.MoveFilesOrDirectoriesDialogWithKotlinOptions +import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.ui.KotlinAwareMoveFilesOrDirectoriesDialog import org.jetbrains.kotlin.idea.references.KtReference import org.jetbrains.kotlin.idea.references.KtSimpleNameReference import org.jetbrains.kotlin.idea.references.KtSimpleNameReference.ShorteningMode @@ -64,9 +63,7 @@ import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor -import java.util.ArrayList -import java.util.Collections -import java.util.Comparator +import java.util.* val UNKNOWN_PACKAGE_FQ_NAME = FqNameUnsafe("org.jetbrains.kotlin.idea.refactoring.move.") @@ -325,14 +322,14 @@ public fun moveFilesOrDirectories( val initialTargetDirectory = MoveFilesOrDirectoriesUtil.getInitialTargetDirectory(targetDirectory, elements) - fun doRun(moveDialog: MoveFilesOrDirectoriesDialog?) { + fun doRun(moveDialog: KotlinAwareMoveFilesOrDirectoriesDialog?) { fun closeDialog() { moveDialog?.close(DialogWrapper.CANCEL_EXIT_CODE) } project.executeCommand(MoveHandler.REFACTORING_NAME) { - val selectedDir = if (moveDialog != null) moveDialog.getTargetDirectory() else initialTargetDirectory - val updatePackageDirective = (moveDialog as? MoveFilesOrDirectoriesDialogWithKotlinOptions)?.updatePackageDirective + val selectedDir = if (moveDialog != null) moveDialog.targetDirectory else initialTargetDirectory + val updatePackageDirective = (moveDialog as? KotlinAwareMoveFilesOrDirectoriesDialog)?.updatePackageDirective try { val choice = if (elements.size() > 1 || elements[0] is PsiDirectory) intArrayOf(-1) else null @@ -375,7 +372,7 @@ public fun moveFilesOrDirectories( return } - with(MoveFilesOrDirectoriesDialogWithKotlinOptions(project, ::doRun)) { + with(KotlinAwareMoveFilesOrDirectoriesDialog(project, ::doRun)) { setData(elements, initialTargetDirectory, "refactoring.moveFile") show() }